diff --git a/src/message-runtime-flow.ts b/src/message-runtime-flow.ts index 9fa5aeb..cf957de 100644 --- a/src/message-runtime-flow.ts +++ b/src/message-runtime-flow.ts @@ -1,6 +1,5 @@ import { getLastHumanMessageContent, - getLatestPreviousPairedTaskForChat, getPairedTurnOutputs, getRecentChatMessages, } from './db.js'; @@ -11,7 +10,6 @@ import { buildOwnerPendingPrompt, buildPairedTurnPrompt, buildReviewerPendingPrompt, - type PriorTaskPromptContext, } from './message-runtime-prompts.js'; import { requeuePendingPairedTurn, @@ -35,7 +33,6 @@ import type { NewMessage, PairedTask, PairedRoomRole, - PairedTurnOutput, PairedTurnReservationIntentKind, RegisteredGroup, } from './types.js'; @@ -119,11 +116,6 @@ export function buildPendingPairedTurn(args: { const cursor = lastRaw?.seq ?? lastRaw?.timestamp ?? null; const taskStatus = task.status; const turnOutputs = getPairedTurnOutputs(task.id); - const priorTaskContext = resolvePriorTaskPromptContext( - chatJid, - task, - turnOutputs, - ); const lastTurnOutput = turnOutputs[turnOutputs.length - 1]; const nextTurnAction = resolveNextTurnAction({ taskStatus, @@ -144,7 +136,6 @@ export function buildPendingPairedTurn(args: { turnOutputs, recentHumanMessages, lastHumanMessage, - priorTaskContext, }), channel: resolveChannel(taskStatus), cursor, @@ -196,7 +187,6 @@ export function buildPendingPairedTurn(args: { turnOutputs, recentHumanMessages, lastHumanMessage, - priorTaskContext, }), channel: resolveChannel(taskStatus), cursor, @@ -210,38 +200,6 @@ export function buildPendingPairedTurn(args: { return null; } -function resolvePriorTaskPromptContext( - chatJid: string, - task: PairedTask, - turnOutputs: PairedTurnOutput[], -): PriorTaskPromptContext | null { - if (turnOutputs.length > 0) { - return null; - } - - const previousTask = getLatestPreviousPairedTaskForChat(chatJid, task.id); - if (!previousTask || previousTask.status !== 'completed') { - return null; - } - - const previousOutputs = getPairedTurnOutputs(previousTask.id); - const ownerFinal = [...previousOutputs] - .reverse() - .find((output) => output.role === 'owner')?.output_text; - const reviewerFinal = [...previousOutputs] - .reverse() - .find((output) => output.role === 'reviewer')?.output_text; - - if (!ownerFinal && !reviewerFinal) { - return null; - } - - return { - ownerFinal: ownerFinal ?? null, - reviewerFinal: reviewerFinal ?? null, - }; -} - export async function executePendingPairedTurn(args: { pendingTurn: Exclude; chatJid: string; diff --git a/src/message-runtime-prompts.test.ts b/src/message-runtime-prompts.test.ts index adce36b..f017f27 100644 --- a/src/message-runtime-prompts.test.ts +++ b/src/message-runtime-prompts.test.ts @@ -69,7 +69,6 @@ describe('message-runtime-prompts carry-forward guidance', () => { ], recentHumanMessages: [makeHumanMessage('이제 새 질문')], lastHumanMessage: '이제 새 질문', - priorTaskContext: null, }); expect( @@ -89,7 +88,6 @@ describe('message-runtime-prompts carry-forward guidance', () => { ], recentHumanMessages: [makeHumanMessage('새 owner 질문')], lastHumanMessage: '새 owner 질문', - priorTaskContext: null, }); expect( @@ -115,72 +113,54 @@ describe('message-runtime-prompts carry-forward guidance', () => { ).toBe(false); }); - it('includes previous owner finals in reviewer pending prompts when the current task has no outputs', () => { + it('does not include previous task context in reviewer pending prompts when the current task has no outputs', () => { const prompt = buildReviewerPendingPrompt({ chatJid: 'group@test', timezone: 'UTC', turnOutputs: [], recentHumanMessages: [makeHumanMessage('추가 질문')], lastHumanMessage: '추가 질문', - priorTaskContext: { - ownerFinal: 'DONE\n이전 owner 결론', - reviewerFinal: 'DONE_WITH_CONCERNS\n이전 reviewer 결론', - }, }); - expect(prompt).toContain( + expect(prompt).toContain('추가 질문'); + expect(prompt).not.toContain( 'Background from the previous completed paired task:', ); - expect(prompt).toContain('Previous task owner final:'); - expect(prompt).toContain('이전 owner 결론'); + expect(prompt).not.toContain('Previous task owner final:'); + expect(prompt).not.toContain('이전 owner 결론'); expect(prompt).not.toContain('Previous task reviewer final:'); expect(prompt).not.toContain('이전 reviewer 결론'); }); - it('includes previous owner finals in owner pending prompts when the current task has no outputs', () => { + it('does not include previous task context in owner pending prompts when the current task has no outputs', () => { const prompt = buildOwnerPendingPrompt({ chatJid: 'group@test', timezone: 'UTC', turnOutputs: [], recentHumanMessages: [makeHumanMessage('추가 owner 질문')], lastHumanMessage: '추가 owner 질문', - priorTaskContext: { - ownerFinal: 'DONE\n이전 owner 결론', - reviewerFinal: 'DONE_WITH_CONCERNS\n이전 reviewer 결론', - }, }); - expect(prompt).toContain( + expect(prompt).toContain('추가 owner 질문'); + expect(prompt).not.toContain( 'Background from the previous completed paired task:', ); - expect(prompt).toContain('Previous task owner final:'); + expect(prompt).not.toContain('Previous task owner final:'); + expect(prompt).not.toContain('이전 owner 결론'); expect(prompt).not.toContain('Previous task reviewer final:'); - expect(prompt).toContain('추가 owner 질문'); + expect(prompt).not.toContain('이전 reviewer 결론'); }); - it('omits previous reviewer finals from prior task context', () => { + it('does not reinject previous reviewer output into new reviewer prompts', () => { const prompt = buildReviewerPendingPrompt({ chatJid: 'group@test', timezone: 'UTC', turnOutputs: [], recentHumanMessages: [makeHumanMessage('새 작업')], lastHumanMessage: '새 작업', - priorTaskContext: { - ownerFinal: null, - reviewerFinal: `TASK_DONE -검증한 것: -- CI pass - -남은 항목 (변동 없음): -(관찰) 사용자 직접 functional 검증 -(잠재) 다른 enum 영어 라벨 추가 발견 시 - -현재 시리즈 상태: -#129 Settings IA -#130 Runtime Inventory`, - }, }); + expect(prompt).toContain('새 작업'); expect(prompt).not.toContain('Previous task reviewer final:'); expect(prompt).not.toContain('검증한 것:'); expect(prompt).not.toContain('CI pass'); diff --git a/src/message-runtime-prompts.ts b/src/message-runtime-prompts.ts index 058ca4f..4f15540 100644 --- a/src/message-runtime-prompts.ts +++ b/src/message-runtime-prompts.ts @@ -8,11 +8,6 @@ const CARRIED_FORWARD_OWNER_FINAL_MARKER = const CARRIED_FORWARD_OWNER_FINAL_GUIDANCE = `System note: If you see a message beginning with "${CARRIED_FORWARD_OWNER_FINAL_MARKER}", treat it as background only. Do not repeat, continue, or answer that carried-forward final directly. Respond only to the latest human request and the current task.`; -export interface PriorTaskPromptContext { - ownerFinal?: string | null; - reviewerFinal?: string | null; -} - function turnOutputsToMessages( outputs: PairedTurnOutput[], chatJid: string, @@ -56,35 +51,6 @@ function prependCarriedForwardGuidance( return `${CARRIED_FORWARD_OWNER_FINAL_GUIDANCE}\n\n${prompt}`; } -function truncatePriorTaskFinal(text: string, maxChars = 1200): string { - const normalized = text.trim(); - if (normalized.length <= maxChars) { - return normalized; - } - return `${normalized.slice(0, maxChars)}...`; -} - -function formatPriorTaskPromptContext( - priorTaskContext?: PriorTaskPromptContext | null, -): string { - if (!priorTaskContext) { - return ''; - } - - const sections: string[] = []; - if (priorTaskContext.ownerFinal?.trim()) { - sections.push( - `Previous task owner final:\n---\n${truncatePriorTaskFinal(priorTaskContext.ownerFinal)}\n---`, - ); - } - - if (sections.length === 0) { - return ''; - } - - return `Background from the previous completed paired task:\n${sections.join('\n\n')}\n\n`; -} - export function buildPairedTurnPrompt(args: { taskId: string; chatJid: string; @@ -119,7 +85,6 @@ export function buildReviewerPendingPrompt(args: { turnOutputs: PairedTurnOutput[]; recentHumanMessages: NewMessage[]; lastHumanMessage: string | null | undefined; - priorTaskContext?: PriorTaskPromptContext | null; }): string { if (args.turnOutputs.length > 0) { return prependCarriedForwardGuidance( @@ -136,10 +101,10 @@ export function buildReviewerPendingPrompt(args: { } if (!args.lastHumanMessage) { - return `${formatPriorTaskPromptContext(args.priorTaskContext)}Review the latest owner changes in the workspace.`; + return 'Review the latest owner changes in the workspace.'; } - return `${formatPriorTaskPromptContext(args.priorTaskContext)}User request:\n---\n${args.lastHumanMessage}\n---\n\nReview the latest owner changes in the workspace.`; + return `User request:\n---\n${args.lastHumanMessage}\n---\n\nReview the latest owner changes in the workspace.`; } export function buildOwnerPendingPrompt(args: { @@ -148,7 +113,6 @@ export function buildOwnerPendingPrompt(args: { turnOutputs: PairedTurnOutput[]; recentHumanMessages: NewMessage[]; lastHumanMessage: string | null | undefined; - priorTaskContext?: PriorTaskPromptContext | null; }): string { if (args.turnOutputs.length > 0) { return prependCarriedForwardGuidance( @@ -165,10 +129,10 @@ export function buildOwnerPendingPrompt(args: { } if (!args.lastHumanMessage) { - return `${formatPriorTaskPromptContext(args.priorTaskContext)}Continue the owner turn using the latest reviewer or arbiter feedback.`; + return 'Continue the owner turn using the latest reviewer or arbiter feedback.'; } - return `${formatPriorTaskPromptContext(args.priorTaskContext)}User request:\n---\n${args.lastHumanMessage}\n---\n\nContinue the owner turn using the latest reviewer or arbiter feedback.`; + return `User request:\n---\n${args.lastHumanMessage}\n---\n\nContinue the owner turn using the latest reviewer or arbiter feedback.`; } export function buildArbiterPromptForTask(args: { diff --git a/src/message-runtime.test.ts b/src/message-runtime.test.ts index ff4281f..2aca497 100644 --- a/src/message-runtime.test.ts +++ b/src/message-runtime.test.ts @@ -1994,7 +1994,7 @@ describe('createMessageRuntime', () => { }); }); - it('includes previous task owner final but omits reviewer final in a new reviewer prompt when the current task has no outputs', () => { + it('does not reinject previous task finals in a new reviewer prompt when the current task has no outputs', () => { const chatJid = 'group@test'; const group = makeGroup('claude-code'); const currentTask = { @@ -2015,44 +2015,7 @@ describe('createMessageRuntime', () => { created_at: '2026-03-30T00:00:10.000Z', updated_at: '2026-03-30T00:00:10.000Z', } as any; - const previousTask = { - ...currentTask, - id: 'task-previous-reviewer', - status: 'completed', - completion_reason: 'superseded', - created_at: '2026-03-30T00:00:00.000Z', - updated_at: '2026-03-30T00:00:09.000Z', - } as any; - - vi.mocked(db.getLatestPreviousPairedTaskForChat).mockReturnValue( - previousTask, - ); - vi.mocked(db.getPairedTurnOutputs).mockImplementation((taskId: string) => { - if (taskId === currentTask.id) { - return []; - } - if (taskId === previousTask.id) { - return [ - { - id: 1, - task_id: previousTask.id, - turn_number: 1, - role: 'owner', - output_text: 'DONE\n이전 owner 답변', - created_at: '2026-03-30T00:00:05.000Z', - }, - { - id: 2, - task_id: previousTask.id, - turn_number: 2, - role: 'reviewer', - output_text: 'DONE_WITH_CONCERNS\n이전 reviewer 피드백', - created_at: '2026-03-30T00:00:06.000Z', - }, - ] as any; - } - return []; - }); + vi.mocked(db.getPairedTurnOutputs).mockReturnValue([]); vi.mocked(db.getLastHumanMessageContent).mockReturnValue('추가 질문'); const pending = buildPendingPairedTurn({ @@ -2066,11 +2029,13 @@ describe('createMessageRuntime', () => { }); expect(pending).not.toBeNull(); - expect(pending?.prompt).toContain( + expect(db.getLatestPreviousPairedTaskForChat).not.toHaveBeenCalled(); + expect(pending?.prompt).toContain('추가 질문'); + expect(pending?.prompt).not.toContain( 'Background from the previous completed paired task:', ); - expect(pending?.prompt).toContain('Previous task owner final:'); - expect(pending?.prompt).toContain('이전 owner 답변'); + expect(pending?.prompt).not.toContain('Previous task owner final:'); + expect(pending?.prompt).not.toContain('이전 owner 답변'); expect(pending?.prompt).not.toContain('Previous task reviewer final:'); expect(pending?.prompt).not.toContain('이전 reviewer 피드백'); }); diff --git a/src/web-dashboard-attachments.test.ts b/src/web-dashboard-attachments.test.ts index 6cfdb28..ed030ac 100644 --- a/src/web-dashboard-attachments.test.ts +++ b/src/web-dashboard-attachments.test.ts @@ -11,22 +11,22 @@ const ONE_PIXEL_PNG = Buffer.from( 'base64', ); -const tempFiles: string[] = []; +const tempDirs: string[] = []; afterEach(() => { - for (const file of tempFiles.splice(0)) { - fs.rmSync(file, { force: true }); + for (const dir of tempDirs.splice(0)) { + fs.rmSync(dir, { force: true, recursive: true }); } }); describe('web dashboard attachment previews', () => { it('serves validated direct temp image attachments', async () => { - const filePath = path.join( - os.tmpdir(), - `bar-chart-label-fit-playwright-${Date.now()}.png`, + const tempDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'ejclaw-dashboard-attachment-'), ); + tempDirs.push(tempDir); + const filePath = path.join(tempDir, 'bar-chart-label-fit-playwright.png'); fs.writeFileSync(filePath, ONE_PIXEL_PNG); - tempFiles.push(filePath); const handler = createWebDashboardHandler({ readStatusSnapshots: () => [], getTasks: () => [],