diff --git a/src/message-runtime-prompts.test.ts b/src/message-runtime-prompts.test.ts index f017f27..83b9234 100644 --- a/src/message-runtime-prompts.test.ts +++ b/src/message-runtime-prompts.test.ts @@ -1,12 +1,13 @@ import { describe, expect, it } from 'vitest'; import { + buildArbiterPromptForTask, buildFinalizePendingPrompt, buildOwnerPendingPrompt, buildPairedTurnPrompt, buildReviewerPendingPrompt, } from './message-runtime-prompts.js'; -import type { NewMessage, PairedTurnOutput } from './types.js'; +import type { NewMessage, PairedTask, PairedTurnOutput } from './types.js'; const CARRY_FORWARD_MARKER = '[Carried forward context from the previous task: latest owner final]'; @@ -27,6 +28,7 @@ function makeHumanMessage(content: string): NewMessage { function makeTurnOutput( outputText: string, role: PairedTurnOutput['role'] = 'owner', + overrides: Partial = {}, ): PairedTurnOutput { return { id: 1, @@ -35,6 +37,37 @@ function makeTurnOutput( role, output_text: outputText, created_at: '2026-04-20T00:59:00.000Z', + ...overrides, + }; +} + +function makeTask(overrides: Partial = {}): PairedTask { + return { + id: 'task-1', + chat_jid: 'group@test', + group_folder: 'group', + owner_service_id: 'codex-main', + reviewer_service_id: 'claude', + owner_agent_type: 'codex', + reviewer_agent_type: 'claude-code', + arbiter_agent_type: 'codex', + title: null, + source_ref: null, + plan_notes: null, + review_requested_at: null, + round_trip_count: 1, + owner_failure_count: 0, + owner_step_done_streak: 0, + finalize_step_done_count: 0, + task_done_then_user_reopen_count: 0, + empty_step_done_streak: 0, + status: 'review_ready', + arbiter_verdict: null, + arbiter_requested_at: null, + completion_reason: null, + created_at: '2026-04-20T00:58:00.000Z', + updated_at: '2026-04-20T00:59:00.000Z', + ...overrides, }; } @@ -79,6 +112,26 @@ describe('message-runtime-prompts carry-forward guidance', () => { ); }); + it('keeps reviewer pending prompts output-only when current task outputs exist', () => { + const prompt = buildReviewerPendingPrompt({ + chatJid: 'group@test', + timezone: 'UTC', + turnOutputs: [ + makeTurnOutput('TASK_DONE\n현재 owner 결과', 'owner', { + turn_number: 1, + created_at: '2026-04-20T02:00:00.000Z', + }), + ], + recentHumanMessages: [ + makeHumanMessage('과거 요청을 다시 기준으로 보면 안 됨'), + ], + lastHumanMessage: '과거 요청을 다시 기준으로 보면 안 됨', + }); + + expect(prompt).toContain('현재 owner 결과'); + expect(prompt).not.toContain('과거 요청을 다시 기준으로 보면 안 됨'); + }); + it('prepends a carry-forward warning to owner pending prompts', () => { const prompt = buildOwnerPendingPrompt({ chatJid: 'group@test', @@ -98,6 +151,75 @@ describe('message-runtime-prompts carry-forward guidance', () => { ); }); + it('keeps owner pending prompts output-only when reviewer feedback exists', () => { + const prompt = buildOwnerPendingPrompt({ + chatJid: 'group@test', + timezone: 'UTC', + turnOutputs: [ + makeTurnOutput('TASK_DONE\n현재 owner 결과', 'owner', { + turn_number: 1, + }), + makeTurnOutput('DONE_WITH_CONCERNS\n현재 reviewer 피드백', 'reviewer', { + id: 2, + turn_number: 2, + }), + ], + recentHumanMessages: [makeHumanMessage('이전 작업의 사용자 메시지')], + lastHumanMessage: '이전 작업의 사용자 메시지', + }); + + expect(prompt).toContain('현재 owner 결과'); + expect(prompt).toContain('현재 reviewer 피드백'); + expect(prompt).not.toContain('이전 작업의 사용자 메시지'); + }); + + it('keeps arbiter prompts output-only when turn outputs exist', () => { + const prompt = buildArbiterPromptForTask({ + task: makeTask({ status: 'arbiter_requested' }), + chatJid: 'group@test', + timezone: 'UTC', + turnOutputs: [ + makeTurnOutput('TASK_DONE\nowner 주장', 'owner', { + turn_number: 1, + }), + makeTurnOutput('DONE_WITH_CONCERNS\nreviewer 반박', 'reviewer', { + id: 2, + turn_number: 2, + }), + ], + recentMessages: [makeHumanMessage('예전 유저 지시')], + labeledRecentMessages: [makeHumanMessage('예전 유저 지시')], + }); + + expect(prompt).toContain('owner 주장'); + expect(prompt).toContain('reviewer 반박'); + expect(prompt).not.toContain('예전 유저 지시'); + }); + + it('preserves turn output order instead of timestamp interleaving', () => { + const prompt = buildReviewerPendingPrompt({ + chatJid: 'group@test', + timezone: 'UTC', + turnOutputs: [ + makeTurnOutput('owner output first', 'owner', { + turn_number: 1, + created_at: '2026-04-20T02:00:00.000Z', + }), + makeTurnOutput('reviewer output second', 'reviewer', { + id: 2, + turn_number: 2, + created_at: '2026-04-20T01:00:00.000Z', + }), + ], + recentHumanMessages: [], + lastHumanMessage: null, + }); + + expect(prompt.indexOf('owner output first')).toBeLessThan( + prompt.indexOf('reviewer output second'), + ); + }); + it('does not prepend the warning when there is no carried-forward turn output', () => { const prompt = buildPairedTurnPrompt({ taskId: 'task-1', diff --git a/src/message-runtime-prompts.ts b/src/message-runtime-prompts.ts index 4f15540..f7ec805 100644 --- a/src/message-runtime-prompts.ts +++ b/src/message-runtime-prompts.ts @@ -79,6 +79,17 @@ export function buildPairedTurnPrompt(args: { ); } +function turnOutputsOnlyPrompt( + chatJid: string, + timezone: string, + turnOutputs: PairedTurnOutput[], +): string { + return prependCarriedForwardGuidance( + formatMessages(turnOutputsToMessages(turnOutputs, chatJid), timezone), + turnOutputs, + ); +} + export function buildReviewerPendingPrompt(args: { chatJid: string; timezone: string; @@ -87,17 +98,7 @@ export function buildReviewerPendingPrompt(args: { lastHumanMessage: string | null | undefined; }): string { if (args.turnOutputs.length > 0) { - return prependCarriedForwardGuidance( - formatMessages( - mergeHumanAndTurnOutputMessages( - args.chatJid, - args.recentHumanMessages, - args.turnOutputs, - ), - args.timezone, - ), - args.turnOutputs, - ); + return turnOutputsOnlyPrompt(args.chatJid, args.timezone, args.turnOutputs); } if (!args.lastHumanMessage) { @@ -115,17 +116,7 @@ export function buildOwnerPendingPrompt(args: { lastHumanMessage: string | null | undefined; }): string { if (args.turnOutputs.length > 0) { - return prependCarriedForwardGuidance( - formatMessages( - mergeHumanAndTurnOutputMessages( - args.chatJid, - args.recentHumanMessages, - args.turnOutputs, - ), - args.timezone, - ), - args.turnOutputs, - ); + return turnOutputsOnlyPrompt(args.chatJid, args.timezone, args.turnOutputs); } if (!args.lastHumanMessage) { @@ -145,11 +136,7 @@ export function buildArbiterPromptForTask(args: { }): string { const messages = args.turnOutputs.length > 0 - ? mergeHumanAndTurnOutputMessages( - args.chatJid, - args.recentMessages.filter((message) => !message.is_bot_message), - args.turnOutputs, - ) + ? turnOutputsToMessages(args.turnOutputs, args.chatJid) : args.labeledRecentMessages; return buildArbiterContextPrompt({