From ab2680ba116a2a7bcae5c2a1642afe6e18648e0c Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 30 Mar 2026 01:32:06 +0900 Subject: [PATCH] feat: route cron output via reviewer bot in paired rooms 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. --- src/index.ts | 17 +++++++++++++++++ src/task-scheduler.ts | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 5bc9de9..752a486 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { DATA_DIR, IDLE_TIMEOUT, POLL_INTERVAL, + REVIEWER_AGENT_TYPE, SERVICE_ID, SERVICE_AGENT_TYPE, isSessionCommandSenderAllowed, @@ -46,6 +47,7 @@ import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js'; import { startIpcWatcher } from './ipc.js'; import { findChannel, + findChannelByName, formatOutbound, normalizeMessageForDedupe, } from './router.js'; @@ -437,6 +439,15 @@ async function main(): Promise { } // Start subsystems (independently of connection handler) + // Resolve the reviewer channel so cron output in paired rooms is posted + // via the reviewer bot — the owner then treats it as a peer request. + const reviewerChannelName = + REVIEWER_AGENT_TYPE === 'claude-code' ? 'discord' : 'discord-review'; + const reviewerChannelForCron = findChannelByName( + channels, + reviewerChannelName, + ); + startSchedulerLoop({ registeredGroups: () => registeredGroups, getSessions: () => sessions, @@ -445,6 +456,12 @@ async function main(): Promise { queue.registerProcess(groupJid, proc, processName, ipcDir), sendMessage: (jid, rawText) => sendFormattedChannelMessage(channels, jid, rawText), + sendMessageViaReviewerBot: reviewerChannelForCron + ? async (jid, rawText) => { + const text = formatOutbound(rawText); + if (text) await reviewerChannelForCron.sendMessage(jid, text); + } + : undefined, sendTrackedMessage: (jid, rawText) => sendFormattedTrackedChannelMessage(channels, jid, rawText), editTrackedMessage: (jid, messageId, rawText) => diff --git a/src/task-scheduler.ts b/src/task-scheduler.ts index 54833c1..7cbad9a 100644 --- a/src/task-scheduler.ts +++ b/src/task-scheduler.ts @@ -20,6 +20,7 @@ import { deleteTask, getDueTasks, getTaskById, + isPairedRoomJid, logTaskRun, updateTask, updateTaskAfterRun, @@ -161,6 +162,8 @@ export interface SchedulerDependencies { ipcDir: string, ) => void; sendMessage: (jid: string, text: string) => Promise; + /** Send a message via the reviewer bot identity so the owner treats it as a peer request. */ + sendMessageViaReviewerBot?: (jid: string, text: string) => Promise; sendTrackedMessage?: (jid: string, text: string) => Promise; editTrackedMessage?: ( jid: string, @@ -402,7 +405,13 @@ async function runTask( const outputText = getAgentOutputText(streamedOutput); if (outputText) { attemptResult = outputText; - await deps.sendMessage(task.chat_jid, outputText); + // In paired rooms, post cron output via reviewer bot so the + // owner treats it as a peer request and acts on it. + const send = + isPairedRoomJid(task.chat_jid) && deps.sendMessageViaReviewerBot + ? deps.sendMessageViaReviewerBot + : deps.sendMessage; + await send(task.chat_jid, outputText); } if (streamedOutput.status === 'error') {