Commit Graph

160 Commits

Author SHA1 Message Date
Eyejoker
c89242b6dd fix: harden stripToolCallLeaks regex for CJK/non-ASCII garbage tokens
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>
2026-03-25 18:06:31 +09:00
Eyejoker
b6d3f879ef refactor: add recovery mode + shared JSON/error/fetch utilities
- GroupQueue recovery mode: limit to 3 concurrent agents for 60s after restart
  to prevent API rate-limit storms (configurable via env vars)
- getErrorMessage: replace 14 occurrences of instanceof Error pattern
- readJsonFile/writeJsonFile: replace 19 occurrences of JSON+fs pattern
- fetchWithTimeout: replace 3 occurrences of AbortController pattern

354/354 tests passing
2026-03-25 07:08:49 +09:00
Eyejoker
f4b02af4dd feat: restore OAuth token auto-refresh with multi-account support
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
2026-03-24 15:35:37 +09:00
Eyejoker
816b0a39cc feat: Codex account rotation + fix rate-limit detection
- 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
2026-03-23 23:06:17 +09:00
Eyejoker
d378598f2e feat: Claude OAuth token rotation on rate-limit
- 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)
2026-03-23 22:01:06 +09:00
Eyejoker
42d721a9ec fix claude turn teardown and start CI watchers immediately 2026-03-23 08:55:48 +09:00
Eyejoker
d28b6151a8 chore: rebrand runtime and docs to EJClaw 2026-03-23 08:09:15 +09:00
Eyejoker
f5eae07657 fix: use resolved ipc dirs for recovery flow 2026-03-23 07:18:47 +09:00
Eyejoker
9def49f7fb fix: isolate watcher task runtimes 2026-03-23 07:16:07 +09:00
Eyejoker
a2db8f6c0e refactor: extract dashboard and task helpers 2026-03-23 06:59:59 +09:00
Eyejoker
898c77fcfc refactor: consolidate runtime helpers 2026-03-23 06:39:26 +09:00
Eyejoker
7c6019dea3 refactor: drop idle queue state 2026-03-23 04:57:13 +09:00
Eyejoker
28eb8c6cb1 fix: harden restart recovery flow 2026-03-23 01:00:14 +09:00
Eyejoker
a1324726b1 style: format after runtime unification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 00:44:04 +09:00
Eyejoker
d4fc498268 refactor: unify runtime into single message-runtime path
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>
2026-03-23 00:43:57 +09:00
Eyejoker
badb900a23 style: format files after pre-commit prettier run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 00:11:44 +09:00
Eyejoker
b647502a10 feat: add seq cursor, work_items, provider fallback, and delivery reliability
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>
2026-03-23 00:11:32 +09:00
Eyejoker
9b741be902 fix: resolve session race condition and follow-up turn output loss
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>
2026-03-21 12:29:00 +09:00
Eyejoker
6eb72ad499 Merge origin/main into chore/nanoclaw-recursive-room 2026-03-20 03:48:29 +09:00
Eyejoker
f281a2ee52 style: apply formatter to runtime files 2026-03-20 03:42:22 +09:00
Eyejoker
a293a701f4 fix: restore regressions from discord-only refactor 2026-03-20 03:41:53 +09:00
Eyejoker
bb0628e8f4 refactor: remove legacy container and non-discord remnants 2026-03-20 01:07:46 +09:00
Eyejoker
41afcb91cf feat: integrate Memento MCP for shared memory across agents
- 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
2026-03-20 00:12:43 +09:00
Eyejoker
f2ad1331a9 Rebrand NanoClaw to EJClaw 2026-03-19 03:31:41 +09:00
Eyejoker
ea03956d0a feat: show session ID in dashboard status display
Add session label (last 8 chars) next to each group status.
Export buildStatusContent and DashboardOptions for testing.
2026-03-16 05:36:30 +09:00
Eyejoker
7e77f37948 refactor: extract dashboard and message-runtime from index.ts
- Extract dashboard code into src/dashboard.ts
- Extract message runtime into src/message-runtime.ts
- Improve group-queue with better concurrency handling
- Update agent-runner with enhanced env/config passing
- Simplify DB operations and cleanup unused code
- Update README for Codex SDK architecture
2026-03-16 05:08:19 +09:00
Eyejoker
e23315ed48 feat: usage/server dashboard with progress bars and aligned labels 2026-03-15 22:37:40 +09:00
Eyejoker
9a6d9f4c4b revert: roll back DB merge, keep useful improvements
Fully revert DB merge patch (WAL, composite PK, SERVICE_ID in IPC/
router_state/sessions). Return to proven split-DB architecture.

Keep:
- Docker/Apple Container cleanup
- Usage dashboard CLAUDE_CODE_OAUTH_TOKEN fallback
- Codex runner heartbeat
- Agent-runner stderr timeout reset
2026-03-15 22:35:53 +09:00
Eyejoker
6f4b6f8df7 feat: prepare DB for shared access (WAL, service partitioning)
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.
2026-03-15 19:26:34 +09:00
Eyejoker
35356dd8a7 refactor: remove token-refresh, use env-based auth only
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.
2026-03-14 19:09:40 +09:00
Eyejoker
7a726c335d feat: purge status channel on startup for clean dashboard
- 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
2026-03-14 00:52:41 +09:00
Eyejoker
d06a835163 fix: usage dashboard - remove title, use system uptime, handle null limitName, drop seconds from timestamp
- 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
2026-03-14 00:50:29 +09:00
Eyejoker
e0fcf836f4 feat: gate usage dashboard behind USAGE_DASHBOARD env var
Only one service needs to show system resources + API usage.
Set USAGE_DASHBOARD=true on the primary service only.
2026-03-13 23:51:31 +09:00
Eyejoker
cf75a1b993 feat: API usage in live dashboard + Discord channel names
- 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)
2026-03-13 23:48:36 +09:00
Eyejoker
7dcb3fe1e4 feat: status dashboard categories/ordering + live usage dashboard
- 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)
2026-03-13 23:42:45 +09:00
Eyejoker
27d4ea05dc feat: add status dashboard for live agent monitoring
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
2026-03-13 23:05:32 +09:00
Eyejoker
09a089ccaf refactor: unify skill sync, remove commands directory
- 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
2026-03-13 17:03:28 +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
68691b7ea9 style: apply prettier formatting 2026-03-11 01:01:56 +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
955a2901c8 feat: add 'both' agent type for shared Claude Code + Codex channels
- 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
2026-03-10 23:58:13 +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
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
Gabi Simons
632713b208 feat: timezone-aware context injection for agent prompts (#691)
* 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 #483
Closes #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>
2026-03-06 18:28:29 +02:00
glifocat
df2bac61f0 fix: format src/index.ts to pass CI prettier check (#711)
Closes #710

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 20:51:40 +02:00
gavrielc
1436186c75 fix: rename _chatJid to chatJid in onMessage callback
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>
2026-03-04 18:07:13 +02:00