diff --git a/src/message-agent-executor.test.ts b/src/message-agent-executor.test.ts index 6908b4e..2df7f9c 100644 --- a/src/message-agent-executor.test.ts +++ b/src/message-agent-executor.test.ts @@ -48,7 +48,11 @@ vi.mock('./platform-prompts.js', () => ({ vi.mock('./db.js', () => ({ createServiceHandoff: vi.fn(), getAllTasks: vi.fn(() => []), + getLastHumanMessageSender: vi.fn(() => null), getLatestOpenPairedTaskForChat: vi.fn(() => undefined), + getLatestTurnNumber: vi.fn(() => 0), + getPairedTaskById: vi.fn(() => undefined), + insertPairedTurnOutput: vi.fn(), })); vi.mock('./service-routing.js', () => ({ diff --git a/src/message-agent-executor.ts b/src/message-agent-executor.ts index a9f5a29..e28008a 100644 --- a/src/message-agent-executor.ts +++ b/src/message-agent-executor.ts @@ -131,7 +131,8 @@ export async function runAgentForGroup( group.agentType, ); // Arbiter always starts fresh — never resume a previous session - const sessionId = activeRole === 'arbiter' ? undefined : sessions[sessionFolder]; + const sessionId = + activeRole === 'arbiter' ? undefined : sessions[sessionFolder]; const memoryBriefing = sessionId ? undefined : await buildRoomMemoryBriefing({ @@ -1047,6 +1048,29 @@ export async function runAgentForGroup( } } + // Notify user when paired task reaches a terminal state that requires attention. + if (pairedExecutionContext) { + const finishedTask = getPairedTaskById(pairedExecutionContext.task.id); + if (finishedTask?.status === 'completed' && finishedTask.completion_reason) { + const sender = getLastHumanMessageSender(chatJid); + const mention = sender ? `<@${sender}>` : ''; + const notifications: Record = { + done: `${mention} ✅ 작업 완료.`, + escalated: `${mention} ⚠️ 자동 해결 불가 — 확인이 필요합니다.`, + arbiter_escalated: `${mention} ⚠️ 중재자 판단: 사람 개입이 필요합니다.`, + }; + const message = notifications[finishedTask.completion_reason]; + if (message) { + await args.onOutput?.({ + status: 'success', + result: message, + output: { visibility: 'public', text: message }, + phase: 'final', + }); + } + } + } + // After owner/reviewer completes, enqueue the next turn so // the message loop picks it up without waiting for a new message. // Skip if owner produced no output — likely interrupted by /stop.