Remove previous owner final prompt carryover
This commit is contained in:
@@ -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<PendingPairedTurn, null>;
|
||||
chatJid: string;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 피드백');
|
||||
});
|
||||
|
||||
@@ -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: () => [],
|
||||
|
||||
Reference in New Issue
Block a user