Commit Graph

704 Commits

Author SHA1 Message Date
Eyejoker
f51f282eb1 chore: update agent-runner package-lock.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 22:31:57 +09:00
Eyejoker
57f2045ca6 style: fix prettier formatting in github-ci and scheduler tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 22:31:52 +09:00
Eyejoker
33fcf9c788 feat: add host-driven GitHub Actions CI watcher (Level 3)
Replace LLM-per-tick polling with direct `gh api` calls for GitHub
Actions watchers. New `ci_provider` discriminator column routes tasks
to either the host-driven path (zero LLM tokens) or the existing
generic LLM path. GitHub watchers poll at 15s intervals (min 10s)
via `checkGitHubActionsRun()` in the scheduler, with completion
messages sent directly to chat on terminal state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 22:31:42 +09:00
Eyejoker
07c1437055 style: fix prettier formatting in ipc processing loop 2026-03-25 22:00:52 +09:00
Eyejoker
691dbc3310 fix: claim IPC files before processing to avoid duplicates 2026-03-25 22:00:36 +09:00
Eyejoker
fad85ef027 style: fix prettier formatting in db migration 2026-03-25 21:54:27 +09:00
Eyejoker
c3c75882e4 fix: add watcher TTL and cleanup task-scoped artifacts 2026-03-25 21:54:15 +09:00
Eyejoker
113c540f10 style: fix prettier formatting in task-scheduler test 2026-03-25 21:41:08 +09:00
Eyejoker
24d93d5c78 fix: fail fast on missing host IPC dir for task runtimes 2026-03-25 21:40:34 +09:00
Eyejoker
5620301024 fix: pass EJCLAW_HOST_IPC_DIR to MCP server env for both runners
Watcher tasks get task-scoped IPC dirs (data/ipc/{group}/tasks/{taskId}/),
but the MCP server used HOST_IPC_DIR to write IPC files. Without
EJCLAW_HOST_IPC_DIR, it fell back to the task-scoped path, writing
cancel_task/send_message files to nested dirs the IPC watcher never scans.

This caused 11 zombie watchers stuck in infinite loops — self-cancel
calls silently failed with ~4000 stranded IPC files accumulating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 21:05:22 +09:00
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
6806001ed6 style: fix prettier formatting after type union refactor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:46:54 +09:00
Eyejoker
94eeaa5e3e refactor: introduce AgentTriggerReason type union as SSOT for error reason strings
Replace scattered reason string literals with a centralized type hierarchy:
- AgentTriggerReason: all possible trigger reasons
- ClaudeRotationReason, CodexRotationReason, NoFallbackCooldownReason:
  derived subtypes for specific contexts
- AgentErrorClassification, FallbackTriggerResult, CodexRotationTriggerResult:
  discriminated unions for compile-time narrowing

Remove dead export isUsageExhausted() (superseded by
isPrimaryNoFallbackCooldownActive). Replace NO_FALLBACK_COOLDOWN_REASONS
Set with isNoFallbackCooldownReason() type guard.

Add tests for agent-error-detection and provider-retry (423 total passing).

Known limitation: 6 `as CodexRotationReason` casts in Codex rotation paths
where streamed trigger reason is AgentTriggerReason but runtime value is
always CodexRotationReason-compatible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:46:43 +09:00
Eyejoker
b86fd1ad89 style: fix prettier formatting in streamed-output-evaluator ternary chain
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:24:25 +09:00
Eyejoker
60a9fe86ec fix: detect org-access-denied errors, suppress chat output, and rotate Claude tokens
When a Claude account is suspended, the API returns "Your organization does
not have access to Claude" on the success path or "Failed to authenticate.
API Error: 403 terminated" on the error path. Both are now classified as
'org-access-denied', suppressed from Discord output, and trigger automatic
token rotation. If all tokens are exhausted, enters cooldown without Kimi
fallback (same as usage-exhausted and auth-expired).

Changes:
- agent-error-detection: add isClaudeOrgAccessDeniedMessage(), expand
  classifyClaudeAuthError() and shouldRotateClaudeToken()
- streamed-output-evaluator: detect org-access-denied in success-path chain
- provider-fallback: add NO_FALLBACK_COOLDOWN_REASONS set and
  isPrimaryNoFallbackCooldownActive() helper
- provider-retry: handle org-access-denied in rotation loop
- message-agent-executor / task-scheduler: use generalized no-fallback
  cooldown check
- Tests: +8 test cases across 5 test files (415 total passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:24:10 +09:00
Eyejoker
3e1e032346 style: fix prettier formatting in formatting tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:51:25 +09:00
Eyejoker
273d52d3da fix: unify agentType value domain to 'claude-code' | 'codex'
The streamed-output-evaluator used 'claude' while the rest of the
codebase used 'claude-code'. Align the evaluator type and all call
sites (message-agent-executor, task-scheduler) to use the canonical
'claude-code' | 'codex' domain consistently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:51:09 +09:00
Eyejoker
8fee2ab3fc fix: strip leaked tool-call serialization text in formatOutbound()
When Codex enters a degenerate loop, it emits tool-call intent as
plaintext (e.g. `to=functions.exec_command code {"cmd":"..."}`) instead
of actual tool calls. Add stripToolCallLeaks() defense layer to remove
these patterns before they reach Discord. Pure garbage output becomes
empty and is suppressed entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:51:03 +09:00
Eyejoker
f62aa261e6 refactor: replace boolean trio with explicit RunPhase state machine in GroupQueue
Replace `active/closingStdin/isTaskProcess` booleans with a single `runPhase`
enum (idle | running_messages | running_task | closing_messages). Add
`resetRunState()` to eliminate 7-field reset duplication, and
`assertRunPhaseInvariants()` for runtime validation of field consistency
after every phase transition. Add 3 transition tests covering the full
lifecycle, closeStdin during tasks, and sendMessage phase gating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 12:55:51 +09:00
Eyejoker
61786cacbf test: add streamed-output-evaluator unit tests
22 tests covering all code paths: success/error forwarding,
Claude banner detection (usage-exhausted, auth-expired), duplicate
trigger suppression, auth error suppression, null-result tracking,
fallback/rotation trigger dispatch, and state immutability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 12:40:44 +09:00
Eyejoker
fc85fece0f refactor: split unified-dashboard into usage-rows and codex-collector modules
Extract dashboard-usage-rows.ts (UsageRow, formatResetRemaining,
mergeClaudeDashboardAccounts, buildClaudeUsageRows, extractCodexUsageRows)
and codex-usage-collector.ts (fetchCodexUsage, applyCodexUsageToAccount,
buildCodexUsageRowsFromState, refresh functions returning data instead of
mutating module state). unified-dashboard.ts drops from 926 to 563 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 12:32:41 +09:00
Eyejoker
18788f7f91 chore: add state transition logging 2026-03-25 12:16:09 +09:00
Eyejoker
cc7cfbbc5f refactor: centralize visible phase mapping 2026-03-25 12:04:29 +09:00
Eyejoker
1ad23269fa style: format task scheduler 2026-03-25 11:46:58 +09:00
Eyejoker
eebee00e60 refactor: share streamed output evaluation 2026-03-25 11:46:16 +09:00
Eyejoker
03b9480614 fix: prevent typing indicator leak on agent run exceptions and follow-up race
Two leak paths caused Discord typing indicators to persist indefinitely:

1. If runAgent() threw before turnController.finish(), setTyping(false)
   was never called. Wrap the agent run in try/finally to guarantee cleanup.

2. Follow-up message piping called setTyping(true) fire-and-forget. If
   the Promise resolved after finish()'s setTyping(false), the typing
   interval was recreated with nobody to clear it. Await the call to
   serialize the on/off sequence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 11:18:54 +09:00
Eyejoker
83aedba7f3 Harden usage dashboard and codex auth 2026-03-25 10:40:02 +09:00
Eyejoker
00ffde7623 fix: remove Codex API key auth path, add secret redaction and d7 auto-rotation
- Remove OPENAI_API_KEY from .env and Codex child process env to prevent
  API billing when subscription quota is exhausted
- Remove writeCodexApiKeyAuth entirely — Codex now uses OAuth only
- Add 9-pattern secret redaction in formatOutbound() to prevent key leaks
- Fix Codex usage bucket aggregation: use 'codex' bucket only instead of
  max across all buckets (bengalfox = Codex Spark, not needed)
- Add d7≥100% auto-rotation in updateCodexAccountUsage to skip exhausted
  accounts and prevent API billing fallback
- Add findNextCodexAvailable that checks both rate-limits and d7 usage
- Clean stale apikey auth.json files from all session directories
- Update tests: OAuth-only auth, d7 auto-skip (3 new tests), dashboard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 10:39:42 +09:00
Eyejoker
094278b08e feat: add ontology checklist, verification gate, completion status protocol, and structured stagnation escalation to paired-room prompts 2026-03-25 07:46:53 +09:00
Eyejoker
2865ba07ce feat: add communication discipline to paired-room prompts (fact-based responses, verification-based completion, 3-strike architecture review) 2026-03-25 07:37:51 +09:00
Eyejoker
7928f2968c fix: always edit progress message for intermediate text
Previously, intermediate text was sent as separate messages when
subagents were active, causing message floods. Now always edit
the progress message regardless of subagent state.
2026-03-25 07:37:49 +09:00
Eyejoker
5e75222754 feat: strengthen paired-room prompts with critical review, consensus gate, and assumption surfacing 2026-03-25 07:31:34 +09:00
Eyejoker
db223f0793 fix: clamp bar() and padName() to prevent negative repeat count
Usage dashboard crashed with RangeError: Invalid count value: -7
when pct exceeded 100 or name exceeded maxNameWidth.
2026-03-25 07:18:04 +09:00
Eyejoker
0af8a7c3f5 fix: restore wide file extension list for inbound attachment parsing
The narrowed list (.txt, .md, .csv, .json, .log) was for outbound,
but was mistakenly applied to inbound. Restore full list for user
file uploads (input direction).
2026-03-25 07:15:24 +09:00
Eyejoker
ab408ab998 style: prettier formatting 2026-03-25 07:09:01 +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
a576d623a1 fix: limit inline file parsing to data files only
Restrict Discord attachment inline parsing to .txt, .md, .csv, .json, .log
and text/* content type. Code files (.ts, .tsx, .js, .py, etc.) now show
filename only instead of being inlined into the message.
2026-03-25 05:02:22 +09:00
Eyejoker
5ea3439c5f refactor: extract shared utilities, protocol constants, and retry loop
Phase 3: provider-retry.ts — shared Claude rotation loop (SSOT)
  - retryClaudeWithRotation extracted from message-agent-executor + task-scheduler
  - ~200 lines of duplicated retry logic removed

Phase 4: types.ts — AgentOutputPhase + VisiblePhase unified
Phase 5: types.ts — AgentConfig extended with claudeThinking/claudeThinkingBudget

Utilities (utils.ts):
  - getErrorMessage: 14 occurrences of instanceof Error pattern → 1 function
  - readJsonFile/writeJsonFile: 19 occurrences of JSON+fs pattern → 2 functions
  - fetchWithTimeout: 3 occurrences of AbortController pattern → 1 function
  - formatElapsedKorean: deduplicated from task-watch-status + message-turn-controller

Protocol (agent-protocol.ts):
  - OUTPUT_START/END_MARKER centralized (runners keep local copies with SSOT reference)
  - IMAGE_TAG_RE, IPC constants documented

Runner: show "대화 요약 중..." progress message during auto-compact

Net: -137 lines, 354/354 tests passing
2026-03-25 04:59:49 +09:00
Eyejoker
b9f98fcc19 feat: log token count on auto-compact trigger
Log pre_tokens and trigger type from compact_boundary message
in both main query loop and /compact slash command path.
2026-03-25 04:01:48 +09:00
Eyejoker
0a64e8c008 chore: add pnpm-lock.yaml 2026-03-25 03:57:52 +09:00
Eyejoker
80490ab162 refactor: extract shared error detection and token rotation utilities (SSOT)
- Create agent-error-detection.ts: single source for error classification
  (isClaudeUsageExhaustedMessage, isClaudeAuthExpiredMessage, classifyAgentError, etc.)
- Create token-rotation-base.ts: shared rotation algorithm
  (parseRetryAfterFromError, computeCooldownUntil, findNextAvailable)
- Remove 6 duplicated functions from message-agent-executor and task-scheduler
- Delegate detectFallbackTrigger/detectCodexRotationTrigger to shared classifiers
- Split canFallback into isClaudeAgent/canRotateToken/canFallback (3-way separation)
- Cache env.ts with getEnv() to eliminate repeated disk I/O
- Migrate config, discord, provider-fallback, token-rotation, memento-client to getEnv()

Net: -146 lines, 0 new dependencies, 354/354 tests passing
2026-03-25 03:56:22 +09:00
Eyejoker
906a3dfadb config: add auto-compact settings for Claude and Codex agents
Claude: CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=50 (~500K on 1M context)
Codex: model_auto_compact_token_limit=258000 (matching CLI default)
2026-03-25 03:56:03 +09:00
Eyejoker
744e2ce6e2 style: format agent rotation updates 2026-03-24 17:09:17 +09:00
Eyejoker
8b2c46b89e fix: harden agent shutdown and auth rotation 2026-03-24 17:08:48 +09:00
Eyejoker
77b4ab1c83 fix: use token suffix for usage cache key (prefix is identical)
All OAuth tokens start with "sk-ant-oat01-" so slice(0,12) produced
the same cache key for every token. Use slice(-8) instead.
2026-03-24 15:45:58 +09:00
Eyejoker
6fc1c77418 fix: suppress 401 auth errors from chat output
401 authentication errors are now filtered from Discord messages
and only logged. Prevents "Failed to authenticate" from reaching users.
2026-03-24 15:42:29 +09:00
Eyejoker
55d865586d style: prettier formatting 2026-03-24 15:35:46 +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
991f2bf2fd test: align fallback expectations after merge 2026-03-24 03:56:07 +09:00
Eyejoker
0724914a43 merge: integrate remote token rotation + memory pipeline
Merge remote main (token rotation, usage API, dashboard fixes)
with local memory pipeline changes. Smart quote encoding fixed.
Test alignment pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 03:53:15 +09:00