import { describe, it, expect } from 'vitest'; import { buildLocalConversationContext } from './local-context.js'; import type { LocalTaskComment } from '../db/repository.js'; function comment(id: number, author: string, kind: string, body: string): LocalTaskComment { return { id, taskId: 1, author, kind, body, createdAt: `2026-05-29T00:00:${String(id).padStart(2, '0')}Z`, injectedAt: null }; } describe('buildLocalConversationContext', () => { it('shows the original task with no follow-up when only the initial request exists', () => { const out = buildLocalConversationContext({ comments: [comment(1, 'user', 'request', 'A を調べて')], jobInstruction: 'A を調べて', inputFiles: [], outputFiles: [], }); expect(out).toContain('## タスク'); expect(out).toContain('A を調べて'); expect(out).not.toContain('## 現在のユーザー指示'); }); it('treats the latest interjection as the current instruction', () => { const comments = [ comment(1, 'user', 'request', 'A を調べて'), comment(2, 'agent', 'progress', '調査中...'), comment(3, 'user', 'interjection', 'やっぱり B を優先して'), ]; const out = buildLocalConversationContext({ comments, jobInstruction: 'A を調べて', inputFiles: [], outputFiles: [], }); expect(out).toContain('## 現在のユーザー指示 (これに対応する)'); expect(out).toContain('やっぱり B を優先して'); expect(out).toContain('## オリジナルタスク (参考、対応済みの可能性あり)'); // interjection body also appears in the recent-conversation block expect(out).toContain('[user/interjection] やっぱり B を優先して'); }); it('keeps a user interjection visible even when many agent progress rows would crowd it out', () => { const comments: LocalTaskComment[] = [comment(1, 'user', 'interjection', 'C をやって')]; for (let i = 2; i <= 14; i++) comments.push(comment(i, 'agent', 'progress', `step ${i}`)); const out = buildLocalConversationContext({ comments, jobInstruction: '元のタスク', inputFiles: [], outputFiles: [], }); // last-10 window is all agent rows; the fix must still surface the user message expect(out).toContain('[user/interjection] C をやって'); expect(out).toContain('## 現在のユーザー指示 (これに対応する)'); expect(out).toContain('C をやって'); }); it('truncates long comment bodies in the recent-conversation block', () => { const long = 'x'.repeat(600); // `long` is an OLDER comment so it only appears in the recent block (truncated), // not as the current instruction (which would render the full body by design). const out = buildLocalConversationContext({ comments: [comment(1, 'user', 'request', long), comment(2, 'user', 'request', 'newer')], jobInstruction: 'newer', inputFiles: [], outputFiles: [], }); expect(out).toContain('x'.repeat(500) + '...'); expect(out).not.toContain('x'.repeat(600)); }); it('lists workspace files', () => { const out = buildLocalConversationContext({ comments: [], jobInstruction: 'task', inputFiles: ['a.csv'], outputFiles: ['report.md'], }); expect(out).toContain('input/: a.csv'); expect(out).toContain('output/: report.md'); }); });