The previous regex used \w+ for descriptor tokens between the function
name and JSON body, which only matches ASCII word characters. Model
hallucinations often inject CJK/non-ASCII text (e.g. 彩神争霸) in this
position. Switch to (?:\s+[^\s{}]+)+ to match any whitespace-separated
non-brace tokens regardless of character set.
Also removes stale backward-compat re-exports from index.ts and updates
the test import for composeDashboardContent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restores token-refresh.ts that was deleted in 35356dd. Now works
with multi-account token rotation:
- Refreshes tokens from ~/.claude/.credentials.json (account 0)
and ~/.claude-accounts/{n}/.credentials.json (account 1+)
- Updates token-rotation memory and .env on refresh
- Syncs credentials to session directories
- 5-min check interval, refreshes 30 min before expiry
- forceRefreshToken() export for 401 recovery
- New codex-token-rotation module: rotates between multiple
~/.codex-accounts/{n}/auth.json on rate-limit
- Copies active account auth to session dir before each spawn
- Fix detectFallbackTrigger to match "usage limit" / "hit your limit"
- Fix streamed error detection: remove claude-only guard so codex
rate-limit errors are caught even when output.status is "success"
- Add rotation in both task-scheduler and message-agent-executor
- New token-rotation module: stores multiple tokens from
CLAUDE_CODE_OAUTH_TOKENS env var (comma-separated)
- On rate-limit, rotates to next healthy token before falling
back to Kimi provider
- Also fixes: intermediate text routes to progress message
when no subagents active (prevents message flooding)
Remove duplicate processGroupMessages, runAgent, startMessageLoop, and
recoverPendingMessages from index.ts. All message processing now flows
through createMessageRuntime() in message-runtime.ts, eliminating the
dual-path issue that caused the v1 follow-up turn reset bug.
index.ts reduced from ~2300 to ~1210 lines.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major reliability improvements to the message processing pipeline:
- Add messages.seq monotonic cursor replacing timestamp-based cursors,
preventing message loss from timestamp collisions with LIMIT queries
- Add work_items table separating agent production from delivery,
enabling delivery retry without re-running the agent
- Propagate Discord send failures instead of silently swallowing them
- Add Claude 429 → Kimi K2.5 automatic provider fallback with cooldown
- Fix follow-up turn state reset in live index.ts path (not just
message-runtime.ts) to prevent final output from being lost
- Add restart context tracking for graceful restart announcements
- Lazy migration from timestamp cursors to seq cursors for existing data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs fixed:
1. agent-runner error path race condition: when agent process exited
with error, the promise resolved immediately without waiting for
outputChain, allowing a late wrappedOnOutput to re-persist a stale
session ID after clearSession had already run.
2. follow-up turn state not resetting: when a follow-up IPC message
joined an active run, turn-level flags (finalOutputSentToUser,
sawNonProgressOutput) from the first turn prevented the last
progress from being promoted to a final message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add MEMENTO_MCP_SSE_URL/ACCESS_KEY/REMOTE_PATH to readEnvFile
- Merge .env vars into cleanEnv for runner process inheritance
- Claude Code runner: add memento-mcp via mcp-remote in mcpServers
- Codex runner: inject memento-mcp section into config.toml
- Various improvements: CI watch, task scheduler, DB, IPC auth
Phase 0-2 of data unification:
- Enable WAL mode + busy_timeout for safe concurrent access
- router_state: keys prefixed with SERVICE_ID (lazy migration)
- sessions: composite PK (group_folder, agent_type)
- registered_groups: filtered by SERVICE_AGENT_TYPE on load
- Logger: service name tag for unified log streams
All changes are backward-compatible with the current split setup.
Actual directory merge (Phase 3-4) is a separate step.
Delete token-refresh.ts and credentials.json sync. Auth is now
via CLAUDE_CODE_OAUTH_TOKEN in .env (1-year setup-token).
No more auto-refresh loop or credentials.json dependency.
- Add purgeChannel() to Channel interface and DiscordChannel
- Uses bulkDelete for recent messages, individual delete for old ones
- Called once at startup before creating fresh dashboard messages
- Remove "📊 Usage 보고" title line
- Use os.uptime() instead of process.uptime() for actual server uptime
- Handle null limitName in Codex rate limits (fallback to limitId or 'Codex')
- Fix Codex usage parsing: extract from rateLimitsByLimitId object
- Fix formatResetKST to handle unix timestamps (number type)
- Remove seconds from update timestamp display
- Fetch Claude Code usage via OAuth API (five_hour/seven_day)
- Fetch Codex usage via app-server JSON-RPC (rateLimits/read)
- System resources: CPU%, memory, disk in GB with emoji thresholds
- Show actual Discord channel names (#name) instead of DB-stored names
- Guard against overlapping usage updates (async API calls)
- Add getChannelMeta to Discord channel (batch guild fetch)
- Status dashboard groups by Discord category, sorts by position
- Add live usage dashboard (CPU, memory, disk, uptime) with message edit
- Fix 4 failing discord.test.ts tests (sendMessage format, image fetch mock)
Dedicated Discord channel shows per-group agent status with live
elapsed time. Single message is edited every 10s instead of spamming.
- GroupQueue tracks startedAt timestamps and exposes getStatuses()
- Channel interface gets editMessage/sendAndTrack for message editing
- STATUS_CHANNEL_ID env var to configure the dashboard channel
- Shows processing/idle/waiting/inactive per registered group
- Remove commands/ sync from agent-runner (skills/ is SSOT for both agents)
- Remove ~/.codex/skills/ as source (use ~/.claude/skills/ only)
- Codex uses $skill prefix, Claude Code uses /skill — both read from skills/
- Minor formatting fixes in config.ts, group-queue.test.ts, index.ts
- 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
- 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
- AgentType now supports 'both' — runs both agents sequentially
- Each agent gets its own folder suffix (_cc / _codex) for isolated sessions
- Claude Code bot handles message storage for 'both' channels
- Responses routed through the matching bot for each agent type
- 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
- 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
- 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
* 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>
* feat: per-group timezone architecture with context injection (#483)
Implement a comprehensive timezone consistency layer so the AI agent always
receives timestamps in the user's local timezone. The framework handles all
UTC↔local conversion transparently — the agent never performs manual timezone
math.
Key changes:
- Per-group timezone stored in containerConfig (no DB migration needed)
- Context injection: <context timezone="..." current_time="..." /> header
prepended to every agent prompt with local time and IANA timezone
- Message timestamps converted from UTC to local display in formatMessages()
- schedule_task translation layer: agent writes local times, framework
converts to UTC using per-group timezone for cron, once, and interval types
- Container TZ env var now uses per-group timezone instead of global constant
- New set_timezone MCP tool for users to update their timezone dynamically
- NANOCLAW_TIMEZONE passed to MCP server environment for tool confirmations
Architecture: Store UTC everywhere, convert at boundaries (display to agent,
parse from agent). Groups without timezone configured fall back to the server
TIMEZONE constant for full backward compatibility.
Closes#483Closes#526
Co-authored-by: shawnYJ <shawny011717@users.noreply.github.com>
Co-authored-by: Adrian <Lafunamor@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* style: apply prettier formatting
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: strip to minimalist context injection — global TIMEZONE only
Remove per-group timezone support, set_timezone MCP tool, and all
related IPC handlers. The implementation now uses the global system
TIMEZONE for all groups, keeping the diff focused on the message
formatting layer: mandatory timezone param in formatMessages(),
<context> header injection, and formatLocalTime/formatCurrentTime
helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: drop formatCurrentTime and simplify context header
Address PR review: remove redundant formatCurrentTime() since message
timestamps already carry localized times. Simplify <context> header to
only include timezone name.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: shawnYJ <shawny011717@users.noreply.github.com>
Co-authored-by: Adrian <Lafunamor@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The underscore prefix convention signals an unused parameter, but it's
now actively used by the sender allowlist logic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>