Commit Graph

664 Commits

Author SHA1 Message Date
Eyejoker
14d70f345a refactor: extract resolveActiveRole() as single source of status→role mapping
resolveCursorKey and resolveChannel both duplicated the same
taskStatus→role switch. Now resolveActiveRole() is the single
mapping, and both functions derive from it.
2026-03-30 22:58:00 +09:00
Eyejoker
a91776b067 refactor: centralize channel selection with resolveChannel()
Replace all manual channel selection (useReviewerChannel,
useArbiterChannel, isReviewerWorkItem, isArbiterWorkItem, 3-way
ternary chains) with a single resolveChannel(taskStatus) function
that maps task status to the correct Discord bot channel.
2026-03-30 22:56:40 +09:00
Eyejoker
7ad40276d9 refactor: centralize cursor key resolution with resolveCursorKey()
Replace all manual cursor key construction (hardcoded chatJid:reviewer,
chatJid:arbiter, pairedCursorKey, 3-way ternary) with a single
resolveCursorKey(chatJid, taskStatus) function.

- Removed pairedCursorKey import (no longer needed)
- Removed loopIsReviewerTurn, loopIsArbiterTurn intermediate variables
- All cursor key logic now flows through one function that maps
  task status to the correct role-specific key
2026-03-30 22:54:35 +09:00
Eyejoker
46926f32f3 fix: use role-specific cursor keys in review_ready and arbiter_requested paths
The review_ready and arbiter_requested paths in processGroupMessages
were advancing the default (owner) cursor instead of the role-specific
cursor. This caused the reviewer to miss the owner's messages because
the owner cursor had already moved past them.

Now review_ready advances chatJid:reviewer cursor only, and
arbiter_requested advances chatJid:arbiter cursor only.
2026-03-30 22:50:26 +09:00
Eyejoker
4ccd45e549 fix: arbiter always uses discord-review bot regardless of model 2026-03-30 22:34:49 +09:00
Eyejoker
f3b25b1b40 fix: apply role labels to arbiter conversation context 2026-03-30 22:33:42 +09:00
Eyejoker
25cec18ce8 fix: clear arbiter session on each invocation to prevent verdict bias 2026-03-30 22:30:42 +09:00
Eyejoker
cb79450572 fix: add arbiter verdict compliance to owner and reviewer prompts 2026-03-30 22:28:48 +09:00
Eyejoker
45fe80f5f5 fix: route BLOCKED/NEEDS_CONTEXT through arbiter before escalating
BLOCKED and NEEDS_CONTEXT verdicts went straight to completed,
bypassing the arbiter entirely. Now when arbiter is enabled, these
verdicts trigger arbiter intervention first — the arbiter may
resolve the block or decide to escalate to the user (ESCALATE).
2026-03-30 22:26:51 +09:00
Eyejoker
a043f2b79a feat: implement MAGI 3-agent arbiter system for deadlock resolution
Introduces a third agent role (arbiter) that is summoned on-demand
when owner and reviewer reach a deadlock (same verdict 3+ rounds).

Architecture:
- 3 Discord bots: owner (codex), reviewer (claude), arbiter (claude/codex)
- Arbiter is NOT always-on — only invoked when deadlock detected
- Arbiter renders binding verdict: PROCEED/REVISE/RESET/ESCALATE
- Non-escalate verdicts reset round_trip_count and resume ping-pong
- Backward compatible: ARBITER_AGENT_TYPE unset = existing 2-agent mode

Changes across 13 source files + 7 test files:
- types.ts: PairedRoomRole += 'arbiter', new statuses, ArbiterVerdict type
- config.ts: ARBITER_AGENT_TYPE, ARBITER_SERVICE_ID, ARBITER_DEADLOCK_THRESHOLD
- db.ts: schema migration (arbiter columns in channel_owner + paired_tasks)
- service-routing.ts: arbiter_service_id in lease
- room-role-context.ts: arbiter role detection
- paired-execution-context.ts: deadlock->arbiter, verdict handling
- message-runtime.ts: arbiter turn routing, cursor, sender labeling
- message-agent-executor.ts: arbiter mode, failover
- agent-runner.ts + environment.ts: arbiter container mode
- platform-prompts.ts: arbiter prompt loading

New files:
- prompts/arbiter-paired-room.md: arbiter system prompt
- src/arbiter-context.ts: builds conversation context for arbiter judgment
2026-03-30 22:20:42 +09:00
Eyejoker
58ed721d6e feat: auto-recreate reviewer containers on token rotation
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
2026-03-30 21:51:45 +09:00
Eyejoker
0e528bf471 fix: update test mocks for voice message flag and CI watcher
- discord.test.ts: add IsVoiceMessage to MessageFlags mock, add
  flags.has() to message mock
- paired-execution-context.test.ts: add hasActiveCiWatcherForChat mock
2026-03-30 21:51:28 +09:00
Eyejoker
7ee915bbeb fix: only reset round_trip_count on human messages
The counter was being reset to 0 on every owner turn, including
bot-only ping-pong turns. This prevented the 3-round-trip echo
loop breaker from ever triggering. Now only human messages reset
the counter.
2026-03-30 21:09:41 +09:00
Eyejoker
27bf36c833 fix: break DONE_WITH_CONCERNS echo loop after 3 round trips
When both owner and reviewer keep echoing DONE_WITH_CONCERNS without
making progress, the system now stops the ping-pong after 3 round
trips and sets the task to completed, escalating to the user.
2026-03-30 21:00:31 +09:00
Eyejoker
196c885c9e fix: clarify owner/reviewer action expectations in paired room prompts
Owner was "agreeing" with concerns instead of fixing code. Reviewer
was listing issues without directing the owner to fix them. Now:
- Reviewer: explicitly direct the owner to fix specific issues
- Owner: fix issues when flagged, don't just acknowledge
2026-03-30 20:49:02 +09:00
Eyejoker
fde29db227 fix: break DONE_WITH_CONCERNS echo loop in paired room prompts
Both agents were repeating "agreed, 3 concerns remain" without
taking action. Now DONE_WITH_CONCERNS requires listing specific
actions, and repeating the same concerns for 2+ turns triggers
BLOCKED escalation.
2026-03-30 20:41:32 +09:00
Eyejoker
c954a383d3 fix: route reviewer failover handoffs via reviewer channel
Reviewer failover handoffs were routed through the owner channel,
causing codex-review to run in owner mode instead of reviewer mode.
Now detects reviewer handoffs by reason prefix and routes them
through the correct reviewer channel.
2026-03-30 20:32:20 +09:00
Eyejoker
39e94c47b3 fix: collect handoffs for all service IDs in unified service
The unified service runs all three bots (claude, codex-main,
codex-review) in one process but only queried handoffs for SERVICE_ID
(claude). Reviewer failover handoffs targeting codex-review were
never picked up. Now queries all three service IDs.
2026-03-30 20:26:43 +09:00
Eyejoker
b209061006 fix: use separate cursors for owner and reviewer in paired rooms
Root cause: owner and reviewer shared the same message cursor. When
the owner's turn completed, the cursor advanced past the owner's
message. The reviewer's turn then only saw messages after that cursor,
missing the owner's response entirely.

Now paired rooms use role-aware cursor keys (chatJid for owner,
chatJid:reviewer for reviewer). Each role advances only its own
cursor, so the reviewer always sees the owner's latest messages
and vice versa.
2026-03-30 20:23:16 +09:00
Eyejoker
78fa078921 fix: only reset merge_ready to active when human message is present
The finalize turn (auto-triggered after reviewer approval) was being
treated as a new working turn because merge_ready was unconditionally
reset to active. Now merge_ready is only reset when a human message
is in the batch, preserving the finalize flow for bot-only turns.
2026-03-30 19:48:25 +09:00
Eyejoker
f30b25d2b4 fix: use English role labels (owner/reviewer) for paired room sender names 2026-03-30 19:31:31 +09:00
Eyejoker
c17edd8773 feat: label bot messages with role names in paired rooms
In paired rooms, bot messages now show sender as "오너" or "리뷰어"
instead of the Discord nickname. Uses channel.isOwnMessage() to
identify which bot sent each message, then maps via the channel
lease to determine the role. This ensures agents correctly identify
who is the owner and who is the reviewer regardless of bot nicknames.
2026-03-30 19:30:59 +09:00
Eyejoker
7ec86433ad feat: add comprehensive verification tools to reviewer container
Media: ffmpeg, sox, mediainfo, imagemagick
Build: make, gcc, g++, python3, pip
Utils: jq, file, sqlite3, rsync, zip/unzip, strace
2026-03-30 19:20:39 +09:00
Eyejoker
b1d9b4cd5b feat: add common verification tools to reviewer container
Add ffmpeg, sox, imagemagick, jq, python3 so the reviewer can verify
audio encoding, image assets, and data without claiming tools are
missing.
2026-03-30 19:16:17 +09:00
Eyejoker
0bcfa72b6b fix: include original filename in attachment prompt text
Agents receive `[Audio: original.wav → /path/to/saved.wav]` instead
of just the path, so they know what file it is and where to find it.
2026-03-30 18:41:36 +09:00
Eyejoker
ad34062dbc feat: mount attachments directory in reviewer container
Discord file attachments are downloaded to data/attachments/ on the
host. Mount this directory read-only in the reviewer container at the
same absolute path so both owner and reviewer can access uploaded
files (WAV, images, etc.) using the same path.
2026-03-30 18:40:36 +09:00
Eyejoker
fcd180434d feat: download audio file attachments instead of transcribing them
Voice messages (Discord IsVoiceMessage flag or attachment with duration)
are still transcribed via Whisper. Regular audio file attachments (WAV,
MP3, etc.) are now downloaded to disk and the path is passed to the
agent, matching image attachment behavior.
2026-03-30 18:37:10 +09:00
Eyejoker
e51afc27f0 feat: failover reviewer to codex when Claude reviewer is unavailable
When the reviewer is Claude and hits auth/429/overloaded errors,
hand off the review turn to codex-review instead of swapping
owner/reviewer roles (which is the owner failover path).
2026-03-30 18:15:17 +09:00
Eyejoker
fa8ca476d8 fix: reset task status to active on new human message
When a user sends a new message while the task is in merge_ready,
review_ready, or in_review status, the owner's turn was incorrectly
treated as a finalize turn. This caused premature task completion
(DONE → completed) even when the user wanted to continue working.

Now resets status to active alongside round_trip_count, ensuring
the owner gets a fresh working turn instead of a finalize turn.
2026-03-30 17:59:27 +09:00
Eyejoker
d5d0ee5332 Merge pull request #4 from phj1081/codex/fix-stale-final-leak
fix: isolate stale finals from next runs
2026-03-30 08:09:03 +09:00
Eyejoker
17e92d4c3d fix: isolate stale finals from next runs 2026-03-30 05:23:29 +09:00
Eyejoker
008154b57d fix: inject real OAuth token to reviewer container instead of proxy
The credential proxy cached the OAuth token at startup, but Anthropic
no longer supports raw OAuth Bearer tokens on the API. The Claude Code
SDK handles OAuth internally, so the proxy was breaking auth.

Now injects the real token both at container creation and at each
docker exec, so token rotation is picked up without recreating the
container.
2026-03-30 05:14:03 +09:00
Eyejoker
3281736390 feat: inject Sentry read-only token into reviewer container 2026-03-30 04:21:18 +09:00
Eyejoker
e13c1df520 feat: skip auto-review when active CI watcher exists
When the owner registers a CI watcher and completes the turn, the
review loop no longer triggers. The watcher result will be posted
via the reviewer bot, which the owner picks up and acts on, then
the normal review loop resumes.
2026-03-30 04:11:30 +09:00
Eyejoker
0ae937c4e2 feat: add gh CLI to reviewer container image 2026-03-30 04:03:17 +09:00
Eyejoker
41dc7969ae fix: route CI watcher completion via reviewer bot in paired rooms 2026-03-30 04:01:44 +09:00
Eyejoker
760e222b54 fix: explicitly declare owner role in paired room prompt 2026-03-30 03:58:39 +09:00
Eyejoker
bc5eb506ba Merge pull request #3 from phj1081/codex/fix-remaining-test-debt
test: align prompt and scheduler expectations
2026-03-30 03:56:55 +09:00
Eyejoker
54fc67e283 test: align prompt and scheduler expectations 2026-03-30 03:55:58 +09:00
Eyejoker
f0f665edb8 Merge pull request #2 from phj1081/codex/fix-format-after-pr1
style: fix prettier drift after merge
2026-03-30 03:49:33 +09:00
Eyejoker
5221fb8b33 style: fix prettier drift after merge 2026-03-30 03:44:58 +09:00
Eyejoker
5e4f1fa8da Merge pull request #1 from phj1081/codex/remove-explicit-paired-review-command-path
refactor: remove dead paired review and output suppression paths
2026-03-30 03:35:34 +09:00
Eyejoker
3015c3ea9a feat: mount parent .git for worktree resolution in reviewer container
Git worktrees reference the parent repo's .git directory via absolute
path. Without mounting it, all git commands fail inside the container.
Now reads the .git file, resolves the parent .git path, and mounts it
read-only at the same host path so references resolve naturally.
2026-03-30 03:28:09 +09:00
Eyejoker
8e9975aa2b fix: add log for container CLAUDE.md injection verification 2026-03-30 03:24:07 +09:00
Eyejoker
e863db61a8 refactor: remove dead output suppression path 2026-03-30 03:22:08 +09:00
Eyejoker
e78f0b1968 fix: remove .claude nesting from reviewer session dir path
CLAUDE_CONFIG_DIR included .claude suffix, but the container mounts
the parent directory at /home/node/.claude. This caused double nesting:
/home/node/.claude/.claude/CLAUDE.md — SDK never found the prompts.
2026-03-30 03:20:29 +09:00
Eyejoker
5b8c198e6b fix: remove agent_type filter from getDueTasks for unified service 2026-03-30 03:01:36 +09:00
Eyejoker
96ca9c0655 fix: use in-process Codex usage cache for unified service dashboard 2026-03-30 02:58:28 +09:00
Eyejoker
debcbfccc2 docs: add fallback provider settings to .env.example 2026-03-30 02:56:33 +09:00
Eyejoker
5b8931f94e docs: set recommended model/effort defaults in .env.example 2026-03-30 02:55:48 +09:00