Allows reviewer to verify runtime state files (cron state, configs)
that the owner references by absolute host path. Mounted at the same
host path so absolute path references work inside the container.
prepareContainerSessionEnvironment now writes AGENTS.md (with platform
+ paired-room prompts) to a session-local .codex/ dir alongside auth
credentials from host. CODEX_HOME points to this dir so codex-runner
gets role-appropriate prompts instead of empty instructions.
When forcedAgentType is set (failover), the role's configured model
(e.g. claude-opus-4-6 for reviewer) was injected as CODEX_MODEL,
causing codex SDK to reject it. Now model overrides are only injected
for the primary agent type, not failover runs.
Model/effort keys (CLAUDE_MODEL, CODEX_MODEL, etc.) are now excluded
from docker run creation args and only injected at docker exec time
matching the active agent type. Removes the empty-value override hack.
Container-level env has CLAUDE_MODEL baked in from creation.
Explicitly clear it on docker exec when running codex agent
to prevent 'model not supported with ChatGPT account' errors.
- Build both agent-runner and codex-runner into container image
- Select runner path based on group.agentType at docker exec time
- Update README to role-centric overview, generic MoA references
- Add MoA reference opinions guidance to arbiter prompt
Token refresh updated internal array and .env file but not process.env,
so docker exec -e kept injecting the stale startup token. Now syncs
to process.env after refresh. Container kill on refresh is no longer
needed since docker exec picks up the latest token each turn.
- Replace better-sqlite3 with bun:sqlite (native, no native addon build)
- Change all spawn('node') to spawn('bun') for agent processes
- Update package.json scripts: node→bun, tsx→bun, npm→bun
- Add bun-types for tsc compatibility
- Add bun:sqlite→better-sqlite3 shim for vitest (tests run on Node.js)
- Update Dockerfile: install bun alongside Node.js (CLIs need Node)
- Update setup/platform.ts: getNodePath() resolves bun binary
- Remove better-sqlite3 from production dependencies (devDep only for tests)
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
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.
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.
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.
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.
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
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
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.
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).
- 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
- 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)
- 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
- 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.
- 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
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.
- 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
- 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
- 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
- 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