Resolves the gitea/main <-> deployed-line merge:
- DB migrations: renumber gitea's colliding 019/020 to 021/022
(reviewer_failure_count -> v21, turn_progress_text_compat -> v22) so all
four migrations have distinct versions; update ordered list + bootstrap test.
- paired_tasks INSERT: add the missing VALUES placeholder so both new columns
(reviewer_failure_count + arbiter_intervention_count) bind (26 cols/values).
- index.ts: drop duplicate startUsagePrimer import from the auto-merge.
- discord output: keep the deployed pipeline (attachment rejection notice) and
call sanitizeForOutbound at the channel boundary so prose escaping done in
prepareDiscordOutbound is not double-applied; keep gitea's reviewer
silent-failure cap + router markdown-escape helpers.
- usage-primer/codex-warmup: keep the dawn-hold removal over gitea's primer.
Full test suite: only pre-existing env/bun-path failures remain; no merge regressions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Port the local primer subsystem onto the upstream base by intent:
- add src/usage-primer.ts (KST-slot primer firing Claude + Codex signals)
- extend codex-warmup with the ignoreZeroUsageWindow runtime option used
by the primer so a slot is never skipped just because Codex is partway
through its weekly window
- Codex primer fires unconditionally (maxUsagePct/maxD7UsagePct=100),
mirroring the Claude primer, refreshing usage before selection
- wire startUsagePrimer() into the runtime bootstrap
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Previously formatOutbound stripped `*`, `_`, `~`, `|`, `` ` ``, `#`, `>`
from prose so they wouldn't trigger Discord formatting. That worked but
silently lost information — `**DONE**`, `STEP_DONE`, ~~strike~~ all
arrived in Discord with the source characters missing.
Switch to backslash-escape so the literal source text shows up while
still suppressing the formatting interpretation. Triple-backtick fenced
code blocks are still preserved verbatim.
Escape is non-idempotent (running it twice double-escapes backslashes),
so split the pipeline:
- sanitizeForOutbound: strip internal tags + tool-call leaks + redact
secrets. Use this for intermediate text that will pass through
another formatOutbound call downstream (work-item storage, channel
wrappers, session-command output).
- formatOutbound: sanitize + neutralizeStrayMarkdown. Reserved for the
single Discord-send boundary in channels/discord.ts.
Internal callers (message-turn-controller, session-commands, the
sendFormattedChannelMessage / sendFormattedTrackedChannelMessage /
editFormattedTrackedChannelMessage wrappers in index.ts, and the cron
reviewer-bot path) now use sanitizeForOutbound so the markdown escape
runs exactly once at the channel boundary.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat: refresh dashboard settings UX and Codex feature toggles
Improve settings/tasks mobile layout, model effort validation by agent type, and preset models for GPT 5.5 and Opus 4.7. Store Codex fast mode and goals in config.toml with Claude fastMode session sync and updated docs for Codex 0.133.
Co-authored-by: Cursor <cursoragent@cursor.com>
* style: apply pre-commit formatting after dashboard settings commit
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Limit restart interruption snapshots, announcements, and recovery queueing to groups that were actively processing. Waiting groups remain compatible in stored context but no longer receive restart recovery signals.
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>
Adds a disabled-by-default loopback web dashboard MVP with read-only control-plane views, prompt preview redaction, Vite React UI, and validation coverage.
- Failover is now global (account-level, not per-channel)
- Dashboard shows role model config (Owner/Reviewer/Arbiter + MoA refs)
- Dashboard shows failover status when active
- Auto-clear failover on successful Claude rotation
- Remove per-channel lease writes from failover path
SERVICE_AGENT_TYPE was always 'claude-code' in the unified service,
making it a misleading constant. Each group already has its own
agentType field.
- Removed SERVICE_AGENT_TYPE from config.ts
- Session functions now accept agentType parameter (default: 'claude-code')
- Task scheduler uses task-level agent_type for token rotation decisions
- All fallback defaults changed to 'claude-code' literal
- Logging uses 'unified' instead of the misleading type
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
In paired rooms, cron output was posted by the owner bot. Since the
owner can't respond to its own messages, the cron report just sat
there with no follow-up action.
Now cron output in paired rooms is posted via the reviewer bot. The
owner picks it up as a peer request, analyzes the report, and acts
on it (e.g., fixing Sentry errors). The normal paired review loop
then kicks in to verify the fix.
Proxy was binding to 127.0.0.1 which is unreachable from Docker
containers. Now uses PROXY_BIND_HOST (detects docker0 IP on Linux)
so containers can reach the proxy via host.docker.internal.
- 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)
- Remove UNIFIED_MODE legacy: single service manages all 3 Discord bots
- Add OWNER_AGENT_TYPE / REVIEWER_AGENT_TYPE env vars for configurable agent selection
- Fix Discord channel routing: reviewer output goes through correct bot (discord/discord-review)
- Fix channel name assignment: explicit names prevent agentTypeFilter collision
- Remove silent output suppression system: harness-level protections replace prompt workarounds
- Add reviewer approval detection: DONE marker on first line stops ping-pong
- Include user's original message in reviewer prompt for context
- Fix round_trip_count auto-reset on new user message
- Fix session ID conflict: reviewer doesn't reuse owner's session
- Fix pending progress text flush in runner on close sentinel
- Promote buffered intermediate text to final result when result event has no text
- Remove legacy files: .env.codex.example, .env.codex-review.example, migrate-unify.cjs
- Update CLAUDE.md: server-side build deployment, unified architecture docs
- 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