Commit Graph

20 Commits

Author SHA1 Message Date
ejclaw
03d4c81192 fix: recover Codex rotation auth failures
- mark Codex bearer/refresh failures as terminal auth-expired states

- sync refreshed session auth back to rotation slots and revive refreshed dead_auth slots

- stop paired arbiter retry loops when Codex accounts are unavailable

- add regression coverage for rotation leases, follow-up suppression, and arbiter closure
2026-06-02 00:21:53 +09:00
Eyejoker
e2dd12a235 Codex switch: translate settings-index ↔ rotation-index by auth path (#51)
settings-store lists codex accounts as
  index 0 = ~/.codex/auth.json (default)
  index N = ~/.codex-accounts/{N}/auth.json
but codex-token-rotation only loads ~/.codex-accounts/{N} when those
dirs exist (it ignores ~/.codex/auth.json in that mode), so the
rotation array's indices are off-by-one vs settings indices.

Old buggy behavior:
- "사용중" badge pointed at the wrong account.
- Clicking 전환 on UI #N called setCurrentCodexAccountIndex(N) which
  selected rotation array element N — typically a different
  ~/.codex-accounts/{N+1} entry. Effectively the UI told us a
  different account was now active than what the next codex spawn
  actually used.

Fix
- New findCodexAccountIndexByAuthPath(path) on the rotation module
  exposes a path-based lookup.
- New getActiveCodexSettingsIndex() / setActiveCodexSettingsIndex(idx)
  in settings-store translate via codexAuthPath() ↔
  findCodexAccountIndexByAuthPath().
- /api/settings/accounts.codexCurrentIndex and PUT
  /api/settings/accounts/codex/current both go through the
  settings-side translation, so UI indices are now authoritative.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:47:40 +09:00
Eyejoker
c2a6889567 Codex accounts: 6h auto-refresh, manual refresh, manual switch (#48)
Auto refresh
- New refreshCodexAccount(index) calls https://auth.openai.com/oauth/token
  with grant_type=refresh_token and persists rotated tokens back to
  ~/.codex-accounts/{N}/auth.json. JWT's subscription_active_until
  reflects the latest plan state OpenAI hands back.
- startCodexAccountRefreshLoop() runs every 6h (first run delayed 60s
  after boot to keep startup snappy). Hooked into main()/shutdown()
  alongside the existing claude token refresh loop.

Manual controls (Settings → 계정 → Codex)
- "갱신" button per row: forces an immediate refresh for that account.
- "전체 갱신" button: refreshes all codex accounts in sequence.
- "전환" button: writes data/codex-rotation-state.json so the next codex
  spawn picks the chosen account. Active account marked with a green-
  bordered card and "사용중" badge.

Endpoints
- POST /api/settings/accounts/codex/{i}/refresh
- POST /api/settings/accounts/codex/refresh-all
- PUT  /api/settings/accounts/codex/current  { index }
- GET  /api/settings/accounts now also returns codexCurrentIndex.

Why
JWTs cache subscription state at issue-time. When a Pro plan lapses or
a user upgrades/downgrades, the dashboard kept showing stale data until
the user logged in again. Periodic refresh + explicit "갱신" button
keeps the displayed state honest, and the manual switch unblocks the
user when rotation lands on a known-bad account.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:23:06 +09:00
ejclaw
e036521054 fix: salvage EJClaw runtime hardening
- add dist freshness guard to deploy flow
- preserve message source provenance for IPC injection
- harden Codex usage fallback and workspace package manager detection
- document owner branch return protocol
2026-04-24 15:02:09 +09:00
ejclaw
10e10b499c Complete paired-room SSOT cleanup and logger singleton 2026-04-07 05:12:22 +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
18788f7f91 chore: add state transition logging 2026-03-25 12:16:09 +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
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
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
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
8b2c46b89e fix: harden agent shutdown and auth rotation 2026-03-24 17:08:48 +09:00
Eyejoker
c51d80bd07 style: prettier formatting 2026-03-24 01:37:14 +09:00
Eyejoker
9e091df6f6 fix: scan all Codex limits for max 5h/7d, persist d7 reset times
- Iterate all rate limits per account instead of just display[0]
- Track and persist 7-day reset time separately (resetD7At)
- Show both 5h and 7d cached usage for inactive Codex accounts
2026-03-24 01:28:48 +09:00
Eyejoker
c7a3a56386 feat: scan all Codex accounts on startup + hourly, cache 5h/7d usage
- Scan all Codex accounts by spawning app-server with each auth dir
- Cache primary (5h) and secondary (7d) usage per account
- Show cached usage + reset remaining time on dashboard
- Persist d7 usage in rotation state file
- formatResetRemaining: "Xh Ym 후" / "N일 후" format
- Remove isRateLimited guard for cached display
2026-03-24 01:25:02 +09:00
Eyejoker
60dab43a3f feat: parse retry-after from errors, cache codex usage per account
- Parse "try again at" time from rate-limit errors instead of
  fixed 1-hour cooldown, with 3-min buffer after reset
- Pass error message to rotateToken/rotateCodexToken for parsing
- Cache usage % and reset time per Codex account for dashboard
- Persist cached usage in rotation state file across restarts
- Show rate-limited Codex accounts with cached 100% + reset time
- Fix dashboard text indicators (*!/space) for monospace alignment
2026-03-24 01:16:54 +09:00
Eyejoker
7401626356 feat: persist rotation state across restarts, fix dashboard alignment
- Save currentIndex + rateLimits to JSON file in DATA_DIR
- Restore on startup so rotation survives service restarts
- Replace emoji indicators with text (*/!) for monospace alignment
- Dynamic column width based on longest name
- Show Codex accounts with plan type on dashboard
2026-03-23 23:16:54 +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