Commit Graph

77 Commits

Author SHA1 Message Date
Eyejoker
e649432a40 feat: add per-role model selection via .env (OWNER/REVIEWER/ARBITER_MODEL, _EFFORT, _FALLBACK_ENABLED) 2026-03-30 23:40:36 +09:00
Eyejoker
58ed721d6e feat: auto-recreate reviewer containers on token rotation
When OAuth tokens rotate or refresh, all reviewer containers are
automatically removed. The next reviewer turn recreates them with
the latest token, preventing 401 auth failures.

Uses callback pattern to avoid circular dependencies:
- token-rotation.ts: onTokenRotated(cb)
- token-refresh.ts: onTokenRefreshed(cb)
- index.ts: registers recreateAllReviewerContainers as callback
2026-03-30 21:51:45 +09:00
Eyejoker
ad34062dbc feat: mount attachments directory in reviewer container
Discord file attachments are downloaded to data/attachments/ on the
host. Mount this directory read-only in the reviewer container at the
same absolute path so both owner and reviewer can access uploaded
files (WAV, images, etc.) using the same path.
2026-03-30 18:40:36 +09:00
Eyejoker
008154b57d fix: inject real OAuth token to reviewer container instead of proxy
The credential proxy cached the OAuth token at startup, but Anthropic
no longer supports raw OAuth Bearer tokens on the API. The Claude Code
SDK handles OAuth internally, so the proxy was breaking auth.

Now injects the real token both at container creation and at each
docker exec, so token rotation is picked up without recreating the
container.
2026-03-30 05:14:03 +09:00
Eyejoker
3281736390 feat: inject Sentry read-only token into reviewer container 2026-03-30 04:21:18 +09:00
Eyejoker
3015c3ea9a feat: mount parent .git for worktree resolution in reviewer container
Git worktrees reference the parent repo's .git directory via absolute
path. Without mounting it, all git commands fail inside the container.
Now reads the .git file, resolves the parent .git path, and mounts it
read-only at the same host path so references resolve naturally.
2026-03-30 03:28:09 +09:00
Eyejoker
484c0d7316 fix: mount reviewer session dir matching CLAUDE_CONFIG_DIR path
Container mounted data/sessions/{folder}/ but CLAUDE.md was written
to data/sessions/{folder}-reviewer/.claude/ by paired-execution-context.
The reviewer never saw its prompts (verdict protocol, paired room rules).

Now mounts {folder}-reviewer/ to match, so the CLAUDE.md with platform
and paired room prompts is visible inside the container.
2026-03-30 01:50:29 +09:00
Eyejoker
d024b763f0 fix: prepare CLAUDE.md and roomRoleContext for container reviewer
Container reviewer was running without prompts because the container
path in agent-runner.ts skipped prepareGroupEnvironment() entirely.
This meant no CLAUDE.md (platform + paired room prompts + memory
briefing) and no roomRoleContext were passed to the container.

- Add prepareContainerSessionEnvironment() to write CLAUDE.md, sync
  skills, and ensure settings.json for the reviewer session directory
- Pass roomRoleContext through ReviewerContainerInput so the runner
  can prepend the [ROOM_ROLE] header
- Add roomRoleContext field to ReviewerContainerInput interface
2026-03-30 01:12:47 +09:00
Eyejoker
a215b2471a chore: formatter cleanup and test updates from bot cherry-pick 2026-03-30 00:57:03 +09:00
Eyejoker
6cb0b38ba3 refactor: persistent reviewer containers with docker exec per turn
Instead of docker run --rm per turn (~300ms overhead), containers are
now created once per channel with docker run -d and reused across turns.
Each turn runs via docker exec inside the warm container.

- ensurePersistentContainer: creates or reuses container per channel
- stopReviewerContainer: explicit stop for /clear or shutdown
- Container stays alive with sleep infinity, turns exec node runner
- cleanupOrphans handles stale containers on restart
2026-03-30 00:45:42 +09:00
Eyejoker
60c3dd5fb9 fix: await onOutput chain in container runner to prevent duplicate delivery
Container runner was calling onOutput with fire-and-forget (void),
so work items were still in 'produced' status when the drain loop
checked after container exit. This caused the same message to be
delivered through both reviewer and owner channels.

Now chains onOutput calls and waits for completion before resolving,
matching the host-process runner's outputChain pattern.
2026-03-30 00:05:42 +09:00
Eyejoker
6036fd988b fix: remap host paths to container mount points for EJCLAW_WORK_DIR and CLAUDE_CONFIG_DIR
Host paths passed via envOverrides don't exist inside the container,
causing ENOENT when the SDK tries to spawn with a non-existent CWD.
Now remaps EJCLAW_WORK_DIR to /workspace/project and CLAUDE_CONFIG_DIR
to /home/node/.claude (matching the container mount points).
2026-03-29 23:52:32 +09:00
Eyejoker
c28fba0478 feat: re-review on post-approval changes, container improvements
- Compare git HEAD at reviewer approval vs owner finalize to detect
  new code changes after DONE verdict. Re-trigger reviewer if changed,
  complete if unchanged (commit-only is fine).
- Record approved HEAD in source_ref when reviewer says DONE
- Add pnpm store mount, pre-flight checks, idle timeout for containers
- Clean up orphaned containers on shutdown
2026-03-29 23:00:56 +09:00
Eyejoker
b965a7e43d feat: add pnpm store mount, pre-flight checks, and idle timeout for reviewer containers
- Detect pnpm projects and mount global store read-only so hardlinks resolve
- npm/yarn/bun need no extra mounts (node_modules is self-contained)
- Pre-flight check: verify Docker running + image exists (cached after first call)
- Add activity-based idle timeout (reset on stdout/stderr like host runner)
- Clean up orphaned containers on shutdown (not just startup)
2026-03-29 22:46:20 +09:00
Eyejoker
fcb854e9de fix: move tmpfs to /tmp instead of inside read-only mount 2026-03-29 19:16:23 +09:00
Eyejoker
d01f98bd61 feat: unify 3 bot services into single process
- Add UNIFIED_MODE flag (default on, disable with UNIFIED_MODE=0)
- Register all 3 Discord bots in one process (claude, codex, review)
- Load all registered groups regardless of agent_type (codex/owner priority)
- Start credential proxy and container cleanup at unified startup
- shouldServiceProcessChat returns true in unified mode
- Add findChannelByName for role-based response routing
- Backward compatible: UNIFIED_MODE=0 restores per-service behavior
2026-03-29 18:37:17 +09:00
Eyejoker
fc9f2867b9 refactor: simplify paired review system and add reviewer container isolation
Phase 1 — Strip over-engineering (-3,665 lines):
- DB tables: 7 → 3 (remove executions, approvals, artifacts, events)
- Task statuses: 11 → 5 (active, review_ready, in_review, merge_ready, completed)
- Remove plan governance, event sourcing, gate/verdict, freshness guards
- paired-execution-context: 980 → 299 lines
- Session commands: remove /risk, /plan, /approve-plan, /request-plan-changes

Phase 2 — Container isolation for reviewers:
- Add container-runtime.ts (Docker abstraction)
- Add credential-proxy.ts (API key injection without container exposure)
- Add container-runner.ts (reviewer-specific read-only mount + tmpfs)
- Add container/Dockerfile + agent-runner (Chromium, Claude Code, Codex)
- agent-runner.ts: auto-route reviewer execution to container mode
2026-03-29 18:24:56 +09:00
Eyejoker
bd7f4408ae refactor: rename container references to agent across codebase
- container-runner.ts → agent-runner.ts
- ContainerInput/Output → AgentInput/Output
- ContainerConfig → AgentConfig
- runContainerAgent → runAgentProcess
- CONTAINER_TIMEOUT → AGENT_TIMEOUT (with env fallback)
- MAX_CONCURRENT_CONTAINERS → MAX_CONCURRENT_AGENTS
- containerName → processName, isTaskContainer → isTaskProcess
- DB column container_config kept as-is (backwards compat)
2026-03-13 16:18:36 +09:00
Eyejoker
35277cea0a feat: codex app-server integration, token sync fix, typing fix
- Rewrite codex-runner to use `codex app-server` JSON-RPC instead of
  `codex exec`. Supports streaming (item/agentMessage/delta), session
  persistence (thread/start, thread/resume), and mid-turn message
  injection (turn/steer with IPC polling during execution).
- Fix token refresh not syncing to per-group session directories,
  causing 401 errors on running agents.
- Fix typing indicator staying on when codex returns null result
  by always clearing typing on any streaming callback.
- Update README and CLAUDE.md to reflect dual-service architecture,
  host process mode, and codex app-server integration.
2026-03-13 16:03:08 +09:00
Eyejoker
0515edc93d feat: OAuth auto-refresh, Codex session resume, MCP injection, and secret sanitization
- Add token-refresh module that auto-renews Claude OAuth tokens 30min before expiry
- Fix Codex session loss on service restart by resuming previous sessions
- Inject nanoclaw MCP server into Codex config.toml for send_message/schedule_task
- Sanitize ANTHROPIC_API_KEY and CLAUDE_CODE_OAUTH_TOKEN from Codex subprocess env
2026-03-12 02:47:24 +09:00
Eyejoker
3fd9ce18b5 feat: sync skills to per-group Codex session dirs 2026-03-11 20:06:38 +09:00
Eyejoker
03c1a67649 fix: sync instructions.md to per-group Codex session dirs 2026-03-11 19:01:09 +09:00
Eyejoker
aed051fb96 fix: sync ~/.claude/.credentials.json to per-group session dirs
Without this, per-group credentials become stale when the home
credentials are refreshed elsewhere, causing 401 auth errors.
2026-03-11 16:49:09 +09:00
Eyejoker
b0ef22e702 fix: use CODEX_HOME instead of CODEX_CONFIG_DIR for per-group session isolation
CODEX_CONFIG_DIR is not recognized by Codex CLI. CODEX_HOME is the correct
env var that controls the root directory for sessions, state, and config.
Without this, all groups sharing the same workDir would have their sessions
collide, causing resume --last to pick up wrong group's session.
2026-03-11 05:07:34 +09:00
Eyejoker
7c49bdf6ce feat: per-group model/effort overrides via containerConfig 2026-03-11 04:31:41 +09:00
Eyejoker
8de978ccf6 feat: sync project workDir commands and skills into agent sessions 2026-03-11 02:59:34 +09:00
Eyejoker
aec2cd27af feat: sync global ~/.claude/commands/ into agent sessions 2026-03-11 02:34:20 +09:00
Eyejoker
de0b166b5d feat: sync user's global ~/.claude/skills/ into agent sessions 2026-03-11 02:32:06 +09:00
Eyejoker
021014cc56 feat: add CODEX_MODEL and CODEX_EFFORT env var support for codex-runner 2026-03-11 02:25:20 +09:00
Eyejoker
cb51ef36f9 feat: add per-group workDir for agent working directory override
Agents can now run in a specified project directory instead of the group
folder. The group's CLAUDE.md is still loaded via additionalDirectories.
2026-03-11 02:02:51 +09:00
Eyejoker
57cafc19f8 refactor: dual-instance setup, remove dead container code
- Make STORE_DIR, DATA_DIR, GROUPS_DIR configurable via env vars
  (NANOCLAW_STORE_DIR, NANOCLAW_DATA_DIR, NANOCLAW_GROUPS_DIR)
  for running two instances from one codebase
- Remove broken 'both' agent type sequential loop from processGroupMessages
- Delete dead code: container-runtime, mount-security, credential-proxy,
  Dockerfiles, and related config constants (~1,085 lines removed)
- Add codex instance launchd plist template
2026-03-11 00:50:03 +09:00
Eyejoker
bbe8354bfd fix: add npm-global to PATH for codex CLI, clear typing after response
- Enrich spawned process PATH with ~/.npm-global/bin so codex CLI is
  findable when running under launchd
- Clear typing indicator in streaming callback after sending response,
  instead of waiting for runner process to fully exit
2026-03-10 21:46:36 +09:00
Eyejoker
46f560ed67 fix: load agentType from DB, fix host process PATH and session resume
- Read/write agent_type column in getAllRegisteredGroups/setRegisteredGroup
  (was missing, causing all groups to fall back to claude-code)
- Strip CLAUDECODE env var to prevent nested session detection
- Add node binary path to spawned process PATH for launchd compatibility
- Clear stale container-era sessions that reference /workspace/group
2026-03-10 21:25:28 +09:00
Eyejoker
08dd468692 feat: remove container layer, run agents as direct host processes
- Rewrite container-runner.ts to spawn node processes directly instead
  of Docker/Apple Container
- Runner paths configurable via env vars (NANOCLAW_GROUP_DIR, etc.)
  with container-path defaults for backwards compat
- Pass real credentials directly (no credential proxy needed)
- Remove ensureContainerSystemRunning() and startCredentialProxy()
  from startup
- Add build:runners script for building agent-runner and codex-runner
- Fix TS strict mode errors in agent-runner ipc-mcp-stdio.ts
2026-03-10 21:05:21 +09:00
Eyejoker
4a34a9c802 feat: add Codex CLI support, dual Discord bots, and Discord typing fix
- Add Codex as alternative agent type (agentType: 'codex' per group)
- Codex container runner with session resume and 100-turn limit
- Dual Discord bot support (DISCORD_BOT_TOKEN + DISCORD_CODEX_BOT_TOKEN)
- Each bot handles only its agentType's registered groups
- Discord typing indicator refreshes every 8s (was single-shot, expired after ~10s)
- Agent runner 100-turn limit to prevent infinite bot-to-bot loops
- Apple Container runtime support (convert-to-apple-container skill)
- Session commands (/compact) support
- DB migration for agent_type column
2026-03-10 20:45:48 +09:00
Gabi Simons
13ce4aaf67 feat: enhance container environment isolation via credential proxy (#798)
* feat: implement credential proxy for enhanced container environment isolation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review — bind proxy to loopback, scope OAuth injection, add tests

- Bind credential proxy to 127.0.0.1 instead of 0.0.0.0 (security)
- OAuth mode: only inject Authorization on token exchange endpoint
- Add 5 integration tests for credential-proxy.ts
- Remove dangling comment
- Extract host gateway into container-runtime.ts abstraction
- Update Apple Container skill for credential proxy compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: scope OAuth token injection by header presence instead of path

Path-based matching missed auth probe requests the CLI sends before
the token exchange. Now the proxy replaces Authorization only when
the container actually sends one, leaving x-api-key-only requests
(post-exchange) untouched.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bind credential proxy to docker0 bridge IP on Linux

On bare-metal Linux Docker, containers reach the host via the bridge IP
(e.g. 172.17.0.1), not loopback. Detect the docker0 interface address
via os.networkInterfaces() and bind there instead of 0.0.0.0, so the
proxy is reachable by containers but not exposed to the LAN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: bind credential proxy to loopback on WSL

WSL uses Docker Desktop with the same VM routing as macOS, so
127.0.0.1 is correct and secure. Without this, the fallback to
0.0.0.0 was triggered because WSL has no docker0 interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: detect WSL via /proc instead of env var

WSL_DISTRO_NAME isn't set under systemd. Use
/proc/sys/fs/binfmt_misc/WSLInterop which is always present on WSL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:27:13 +02:00
gavrielc
bae8538695 Fix/shadow env in container (#646)
* fix: shadow .env file in container to prevent agents from reading secrets

The main agent's container mounts the project root read-only, which
inadvertently exposed the .env file containing API keys. Mount /dev/null
over /workspace/project/.env to shadow it — secrets are already passed
via stdin and never need to be read from disk inside the container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: adapt .env shadowing and runtime for Apple Container

Apple Container (VirtioFS) only supports directory mounts, not file
mounts. The previous /dev/null host-side mount over .env crashes with
VZErrorDomain "A directory sharing device configuration is invalid".

- Dockerfile: entrypoint now shadows .env via mount --bind inside the
  container, then drops privileges via setpriv to the host UID/GID
- container-runner: main containers skip --user and pass RUN_UID/RUN_GID
  env vars so entrypoint starts as root for mount --bind
- container-runtime: switch to Apple Container CLI (container), fix
  cleanupOrphans to use container list --format json
- Skill: add Dockerfile and container-runner.ts to
  convert-to-apple-container skill (v1.1.0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: revert src to Docker runtime, keep Apple Container in skill only

The source files should remain Docker-compatible. The Apple Container
adaptations live in the convert-to-apple-container skill and are applied
on demand.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:28:28 +02:00
Lix
51bb32930c feat: add third-party model support (#592)
- Support ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN for custom API endpoints
- Add documentation for third-party/open-source model usage

Co-authored-by: wenglixin <wenglixin@menusifu.cn>
2026-03-01 23:45:40 +02:00
Gabi Simons
11c201088b refactor: CI optimization, logging improvements, and codebase formatting (#456)
* fix(db): remove unique constraint on folder to support multi-channel agents

* ci: implement automated skill drift detection and self-healing PRs

* fix: align registration logic with Gavriel's feedback and fix build/test issues from Daniel Mi

* style: conform to prettier standards for CI validation

* test: fix branch naming inconsistency in CI (master vs main)

* fix(ci): robust module resolution by removing file extensions in scripts

* refactor(ci): simplify skill validation by removing redundant combination tests

* style: conform skills-engine to prettier, unify logging in index.ts and cleanup unused imports

* refactor: extract multi-channel DB changes to separate branch

Move channel column, folder suffix logic, and related migrations
to feat/multi-channel-db-v2 for independent review. This PR now
contains only CI/CD optimizations, Prettier formatting, and
logging improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:13:36 +02:00
gavrielc
495b7df5fc merge: resolve conflict with origin/main
Keep ASSISTANT_NAME import, drop removed GROUPS_DIR import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:03:20 +02:00
gavrielc
77f7423172 fix: pass host timezone to container and reject UTC-suffixed timestamps (#371)
Containers had no TZ set, so any time-aware code inside ran in UTC while
the host interpreted bare timestamps as local time. Now TIMEZONE from
config.ts is passed via -e TZ= to the container args.

Also rejects Z-suffixed or offset-suffixed timestamps in the container's
schedule_task validation, since bare timestamps are expected to be local
time and silently accepting UTC suffixes would cause an offset mismatch.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:23:34 +02:00
Dan Shapiro
107aff850c fix: pass assistantName to container agent instead of hardcoding 'Andy'
The container agent-runner had 'Andy' hardcoded as the sender name in
archived conversation transcripts. This ignored the configurable
ASSISTANT_NAME setting, so users who changed their assistant's name
(via .env or config) would still see 'Andy' in transcripts.

- Add assistantName field to ContainerInput interface (both host and
  container copies)
- Pass ASSISTANT_NAME from config through to container in index.ts
  and task-scheduler.ts
- Thread assistantName through createPreCompactHook and
  formatTranscriptMarkdown in the agent-runner
- Use 'AssistantNameMissing' as fallback instead of 'Andy' so a
  missing name is visible rather than silently wrong

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:22:07 -08:00
Lawyered
c6391cceb1 fix: block group folder path escapes 2026-02-22 21:01:53 +02:00
gavrielc
5fb10645cd fix: mount project root read-only to prevent container escape (#392)
The main group's project root was mounted read-write, allowing the
container agent to modify host application code (e.g. dist/container-runner.js)
to inject arbitrary mounts on next restart — a full sandbox escape.

Fix: mount the project root read-only. Writable paths the agent needs
(group folder, IPC, .claude/) are already mounted separately. The
agent-runner source is now copied into a per-group writable location
so agents can still customize container-side behavior without affecting
host code or other groups.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 20:57:57 +02:00
Stefan Gasser
d336b32460 fix: copy skill subdirectories recursively (#175)
copyFileSync crashes with EISDIR when a skill contains subdirectories
like scripts/. Skills support nested folders (scripts/, examples/,
templates/) per the Claude Code spec. Use fs.cpSync to handle the
complete skill structure.
2026-02-21 17:23:19 +02:00
gavrielc
c6e1bfecc6 refactor: extract runtime-specific code into src/container-runtime.ts (#321)
Move all container-runtime-specific logic (binary name, mount args,
stop command, startup check, orphan cleanup) into a single file so
swapping runtimes only requires replacing this one file.

Neutralize "Apple Container" references in comments and docs that
would become incorrect after a runtime swap. References that list
both runtimes as options are left unchanged.

No behavior change — Apple Container remains the default runtime.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:13:55 +02:00
Koshkoshinsk
802805d2ec Fix/WA reconnect, container perms, assist name in env (#297)
* fix: WA 515 stream error reconnect exiting early before key sync

Pass isReconnect flag on 515 reconnect so the registered-creds check
doesn't bail out before the handshake completes (caused "logging in..."
hang after successful pairing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: container permission errors on Docker with non-default uid

Make /home/node world-writable in the Dockerfile so the SDK can write
.claude.json. Add --user flag matching host uid/gid in container-runner
so bind-mounted files are accessible. Skip when running as root (uid 0),
as the container's node user (uid 1000), or on native Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: write ASSISTANT_NAME to .env during setup

When a custom assistant name is chosen, persist it to .env so config.ts
picks it up at runtime. Uses temp file for cross-platform sed
compatibility (macOS/Linux/WSL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:29:55 +02:00
gavrielc
9261a25531 feat: add is_bot_message column and support dedicated phone numbers (#235)
* feat: add is_bot_message column and support dedicated phone numbers

Replace fragile content-prefix bot detection with an explicit
is_bot_message database column. The old prefix check (content NOT LIKE
'Andy:%') is kept as a backstop for pre-migration messages.

- Add is_bot_message column with automatic backfill migration
- Add ASSISTANT_HAS_OWN_NUMBER env var to skip name prefix when the
  assistant has its own WhatsApp number
- Move prefix logic into WhatsApp channel (no longer a router concern)
- Remove prefixAssistantName from Channel interface
- Load .env via dotenv so launchd-managed processes pick up config
- WhatsApp bot detection: fromMe for own number, prefix match for shared

Based on #160 and #173.

Co-Authored-By: Stefan Gasser <stefan@stefangasser.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: extract shared .env parser and remove dotenv dependency

Extract .env parsing into src/env.ts, used by both config.ts and
container-runner.ts. Reads only requested keys without loading secrets
into process.env, avoiding leaking API keys to child processes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Stefan Gasser <stefan@stefangasser.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:31:57 +02:00
Cole
1a07869329 security: sanitize env vars from agent Bash subprocesses (#171)
Use a PreToolUse SDK hook to prepend `unset ANTHROPIC_API_KEY
CLAUDE_CODE_OAUTH_TOKEN` to every Bash command Kit runs, preventing
secret leakage via env/printenv/echo/$PROC. Secrets are now passed
via stdin JSON instead of mounted env files, closing all known
exfiltration vectors.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:33:39 +02:00
gavrielc
8eb80d4ed0 fix: prevent infinite message replay on container timeout (#164)
Container timeout and idle timeout both fire at 30min, racing the
graceful shutdown. The hard kill returns error status, rolling back
the message cursor even though output was already sent — causing
duplicate messages indefinitely.

- Grace period: hard timeout is now IDLE_TIMEOUT + 30s minimum
- Timeout after output resolves as success (idle cleanup, not failure)
- Don't roll back cursor if output was already sent to user
- Remove src/telegram.ts and config vars (added to PR #156 by mistake)
- Add typecheck step to CI workflow
- Add container-runner timeout behavior tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:25:42 +02:00