kimi-code/packages/transcript/test/layers.test.ts

1538 lines
57 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { filterOpsForGrade, isAppendOnly, redactSnapshotForGrade } from '#/granularity/filterOps';
import { detachGrades, gradeFor, needsResetOnTransition } from '#/granularity/grade';
import { paginateTurns } from '#/pagination/paginate';
import { ViewRegistry } from '#/view/registry';
import { groupMessagesIntoSnapshot, type HistoryContentPart } from '#/history/groupTurns';
import { foldWireRecordFacts, type HistoryWireRecord } from '#/history/foldFacts';
import {
transcriptOperationSchema,
transcriptQuerySchema,
transcriptResponseSchema,
transcriptGradeSpecSchema,
} from '#/contract/schema';
import type { TranscriptItem } from '#/model/item';
import type { AgentTranscriptSnapshot, TranscriptOperation } from '#/ops/operation';
const idLabel = (i: TranscriptItem): string =>
i.kind === 'turn' ? i.turnId : i.kind === 'marker' ? i.markerId : i.refId;
const turnOp = (n: number): TranscriptOperation => ({
op: 'turn.upsert',
turn: { kind: 'turn', turnId: `t${n}`, ordinal: n, state: 'running', origin: { kind: 'user' } },
});
const stepOp: TranscriptOperation = {
op: 'step.upsert',
turnId: 't1',
step: { kind: 'step', stepId: 't1.1', turnId: 't1', ordinal: 1, state: 'running' },
};
const frameOp: TranscriptOperation = {
op: 'frame.upsert',
turnId: 't1',
stepId: 't1.1',
frame: { kind: 'text', frameId: 't1.1.f1', role: 'assistant', text: 'full' },
};
const appendOp: TranscriptOperation = {
op: 'append',
target: { type: 'frame', turnId: 't1', stepId: 't1.1', frameId: 't1.1.f1' },
offset: 0,
text: 'chunk',
};
const promptOp: TranscriptOperation = {
op: 'prompt.upsert',
prompt: { promptId: 'p1', status: 'queued', createdAt: '2026-07-22T00:00:00.000Z' },
};
describe('granularity', () => {
const ops: TranscriptOperation[] = [
turnOp(1),
stepOp,
frameOp,
appendOp,
promptOp,
{ op: 'meta.merge', meta: { activity: 'turn' } },
];
it('off admits nothing', () => {
expect(filterOpsForGrade('off', ops)).toEqual([]);
});
it('turn admits headers and global state only', () => {
expect(filterOpsForGrade('turn', ops).map((op) => op.op)).toEqual([
'turn.upsert',
'prompt.upsert',
'meta.merge',
]);
});
it('block admits step/frame upserts but no appends', () => {
expect(filterOpsForGrade('block', ops).map((op) => op.op)).toEqual([
'turn.upsert',
'step.upsert',
'frame.upsert',
'prompt.upsert',
'meta.merge',
]);
});
it('delta admits everything', () => {
expect(filterOpsForGrade('delta', ops)).toHaveLength(ops.length);
});
it('gradeFor resolves agent override over wildcard default', () => {
const spec = { '*': 'turn', main: 'delta' } as const;
expect(gradeFor(spec, 'main')).toBe('delta');
expect(gradeFor(spec, 'sub-1')).toBe('turn');
expect(gradeFor(undefined, 'main')).toBe('off');
});
it('upgrade needs reset, downgrade does not', () => {
expect(needsResetOnTransition('turn', 'delta')).toBe(true);
expect(needsResetOnTransition('delta', 'turn')).toBe(false);
});
it('detachGrades writes explicit off so a wildcard default cannot resurrect the agent', () => {
expect(detachGrades({ '*': 'delta' }, ['main'])).toEqual({ '*': 'delta', main: 'off' });
expect(detachGrades({ '*': 'delta', main: 'turn' }, ['main'])).toEqual({
'*': 'delta',
main: 'off',
});
});
it('detachGrades deletes a listed wildcard entry', () => {
expect(detachGrades({ '*': 'delta', main: 'turn' }, ['*'])).toEqual({ main: 'turn' });
});
it('detachGrades collapses an all-off spec to undefined', () => {
expect(detachGrades({ main: 'delta' }, ['main'])).toBeUndefined();
expect(detachGrades({ '*': 'delta', main: 'off' }, ['*'])).toBeUndefined();
expect(detachGrades(undefined, ['main'])).toBeUndefined();
});
it('append-only batches are volatile-safe', () => {
expect(isAppendOnly([appendOp])).toBe(true);
expect(isAppendOnly([appendOp, frameOp])).toBe(false);
});
it('redactSnapshotForGrade strips step detail below block, keeps it at block+', () => {
const snapshot: AgentTranscriptSnapshot = {
items: [
{
kind: 'turn',
turnId: 't1',
ordinal: 1,
state: 'completed',
origin: { kind: 'user' },
prompt: 'hi',
steps: [
{
kind: 'step',
stepId: 't1.1',
turnId: 't1',
ordinal: 1,
state: 'completed',
frames: [{ kind: 'text', frameId: 't1.1.f1', role: 'assistant', text: 'body' }],
},
],
},
{ kind: 'marker', markerId: 'm1', marker: 'skill' },
],
tasks: [],
interactions: [
{
interactionId: 'appr-1',
interactionKind: 'approval' as const,
toolCallId: 'c1',
state: 'pending' as const,
},
],
attachments: [
{ attachmentId: 'att_1', mediaType: 'image/png', source: { kind: 'url' as const, url: 'https://example.com/a.png' } },
],
todos: [{ todoId: 'todo', items: [{ title: 'write tests', status: 'in_progress' as const }] }],
prompts: [{ promptId: 'p1', status: 'running' as const, createdAt: '2026-07-22T00:00:00.000Z' }],
meta: {},
};
const turnGrade = redactSnapshotForGrade('turn', snapshot);
expect(turnGrade.interactions).toHaveLength(1);
expect(turnGrade.attachments).toHaveLength(1);
expect(turnGrade.todos).toHaveLength(1);
expect(turnGrade.prompts).toHaveLength(1);
const turn = turnGrade.items[0];
expect(turn?.kind === 'turn' && turn.steps).toEqual([]);
expect(turn?.kind === 'turn' && turn.prompt).toBe('hi');
expect(turnGrade.items[1]?.kind).toBe('marker');
expect(redactSnapshotForGrade('block', snapshot)).toBe(snapshot);
expect(redactSnapshotForGrade('delta', snapshot)).toBe(snapshot);
});
});
describe('paginateTurns', () => {
const items: TranscriptItem[] = [
{ kind: 'marker', markerId: 'm0', marker: 'goal' },
...[1, 2, 3, 4, 5].flatMap((n): TranscriptItem[] => [
{
kind: 'turn',
turnId: `t${n}`,
ordinal: n,
state: 'completed',
origin: { kind: 'user' },
steps: [],
},
{ kind: 'marker', markerId: `m${n}`, marker: 'skill' },
]),
];
it('default page is the newest N turns with trailing segment items', () => {
const page = paginateTurns(items, { pageSize: 2 });
expect(page.items.map(idLabel)).toEqual(['t4', 'm4', 't5', 'm5']);
expect(page.hasMore).toBe(true);
});
it('before_turn pages toward older turns; head marker rides the oldest segment', () => {
const page = paginateTurns(items, { beforeTurn: 't4', pageSize: 2 });
expect(page.items.map(idLabel)).toEqual(['t2', 'm2', 't3', 'm3']);
expect(page.hasMore).toBe(true);
const oldest = paginateTurns(items, { beforeTurn: 't2', pageSize: 5 });
expect(oldest.items[0]).toEqual({ kind: 'marker', markerId: 'm0', marker: 'goal' });
expect(oldest.hasMore).toBe(false);
});
it('after_turn pages toward newer turns without the head unit', () => {
const page = paginateTurns(items, { afterTurn: 't3', pageSize: 2 });
expect(page.items.map(idLabel)).toEqual(['t4', 'm4', 't5', 'm5']);
expect(page.hasMore).toBe(false);
});
it('keeps head non-turn items with the newest page when turns exactly fill it', () => {
const page = paginateTurns(items, { pageSize: 5 });
expect(page.items[0]).toEqual({ kind: 'marker', markerId: 'm0', marker: 'goal' });
expect(page.items.map(idLabel)).toEqual(['m0', 't1', 'm1', 't2', 'm2', 't3', 'm3', 't4', 'm4', 't5', 'm5']);
expect(page.hasMore).toBe(false);
});
it('returns a marker-only timeline as one page with nothing older', () => {
const only = paginateTurns([{ kind: 'marker', markerId: 'm0', marker: 'goal' }], { pageSize: 3 });
expect(only.items.map(idLabel)).toEqual(['m0']);
expect(only.hasMore).toBe(false);
});
});
describe('ViewRegistry', () => {
it('dispatches on view ?? name, origin.kind and marker keys', () => {
const registry = new ViewRegistry<string>({ fallbackTool: 'generic' });
registry.registerTool('read', 'readRenderer');
registry.registerTool('swarm', 'swarmRenderer');
registry.registerInput('cron', 'cronInput');
registry.registerMarker('goal', 'goalMarker');
expect(
registry.resolveTool({ kind: 'tool', frameId: 'f', toolCallId: 'c1', name: 'Read', state: 'done' }),
).toBe('readRenderer');
expect(
registry.resolveTool({ kind: 'tool', frameId: 'f', toolCallId: 'c2', name: 'AgentSwarm', view: 'swarm', state: 'running' }),
).toBe('swarmRenderer');
expect(
registry.resolveTool({ kind: 'tool', frameId: 'f', toolCallId: 'c3', name: 'Bash', state: 'running' }),
).toBe('generic');
expect(registry.resolveInput({ kind: 'cron' })).toBe('cronInput');
expect(registry.resolveInput({ kind: 'user' })).toBeUndefined();
expect(registry.resolveMarker('goal')).toBe('goalMarker');
});
});
describe('contract schemas', () => {
it('roundtrips every op kind', () => {
const ops: TranscriptOperation[] = [
{ op: 'reset', agentId: 'main', snapshot: { items: [], tasks: [], interactions: [], attachments: [], todos: [], prompts: [], meta: {}, hasMoreOlder: true } },
turnOp(1),
stepOp,
frameOp,
appendOp,
{ op: 'marker.upsert', item: { kind: 'marker', markerId: 'm1', marker: 'goal' } },
{ op: 'taskref.upsert', item: { kind: 'taskref', refId: 'r1', taskId: 'task1' } },
{ op: 'task.upsert', task: { taskId: 'task1', kind: 'shell', state: 'running', detached: false, outputTail: '' } },
{
op: 'interaction.upsert',
interaction: { interactionId: 'appr-1', interactionKind: 'approval', toolCallId: 'c1', state: 'pending' },
},
{
op: 'attachment.upsert',
attachment: { attachmentId: 'att_1', mediaType: 'image/png', source: { kind: 'file', fileId: 'f1' } },
},
{ op: 'todo.upsert', todo: { todoId: 'todo', items: [{ title: 'x', status: 'done' }] } },
promptOp,
{ op: 'meta.merge', meta: { goal: { objective: 'x', status: 'active' } } },
{ op: 'items.remove', ids: ['t1'] },
];
for (const op of ops) {
expect(transcriptOperationSchema.parse(op)).toBeDefined();
}
});
it('roundtrips ops carrying the extended wire detail', () => {
const usage = { inputOther: 10, output: 5, inputCacheRead: 3, inputCacheCreation: 2 };
const ops: TranscriptOperation[] = [
{
op: 'reset',
agentId: 'main',
snapshot: {
items: [],
tasks: [],
interactions: [],
attachments: [],
todos: [],
prompts: [
{
promptId: 'p1',
status: 'completed',
userMessageId: 'u1',
content: [{ type: 'text', text: 'hi' }],
createdAt: '2026-07-22T00:00:00.000Z',
finishedAt: '2026-07-22T00:01:00.000Z',
steeredAt: '2026-07-22T00:00:30.000Z',
},
],
meta: {
agent: {
model: 'k2',
thinkingEffort: 'high',
usage: { byModel: { k2: usage }, currentTurn: usage, total: usage },
contextTokens: 1234,
maxContextTokens: 128000,
contextUsage: 0.01,
permission: 'auto',
phase: { kind: 'retrying', turnId: 1, step: 1, stepId: 't1.1', failedAttempt: 1, nextAttempt: 2, maxAttempts: 3, delayMs: 500, since: 1000 },
},
},
},
},
{
op: 'turn.upsert',
turn: {
kind: 'turn', turnId: 't1', ordinal: 1, state: 'failed', origin: { kind: 'user' },
usage: { inputTokens: 12, outputTokens: 5, cachedTokens: 3 },
durationMs: 1500,
error: 'boom',
},
},
{
op: 'step.upsert',
turnId: 't1',
step: {
kind: 'step', stepId: 't1.1', turnId: 't1', ordinal: 1, state: 'interrupted',
usage,
finishReason: 'stop',
timing: {
llmFirstTokenLatencyMs: 120,
llmStreamDurationMs: 900,
llmRequestBuildMs: 5,
llmServerFirstTokenMs: 110,
llmServerDecodeMs: 700,
llmClientConsumeMs: 950,
},
retry: { failedAttempt: 1, nextAttempt: 2, maxAttempts: 3, delayMs: 500, errorName: 'RateLimit', errorMessage: 'slow down', statusCode: 429 },
endReason: 'aborted',
endMessage: 'user pressed escape',
},
},
{
op: 'frame.upsert',
turnId: 't1',
stepId: 't1.1',
frame: {
kind: 'tool', frameId: 't1.1.c1', toolCallId: 'c1', name: 'Bash', state: 'running',
inputText: '{"command":"ls',
progress: { kind: 'progress', text: 'half', percent: 50, customKind: 'bar', customData: { x: 1 } },
},
},
{
op: 'task.upsert',
task: {
taskId: 'task1', kind: 'subagent', state: 'completed', detached: false, outputTail: '',
resultSummary: 'scanned 12 files',
error: 'partial failure',
stateReason: 'waiting for input',
usage,
},
},
{
op: 'meta.merge',
meta: { agent: { model: 'k2', phase: { kind: 'ended', turnId: 1, reason: 'completed', durationMs: 1500, at: 2000 } } },
},
];
for (const op of ops) {
expect(transcriptOperationSchema.parse(op)).toEqual(op);
}
});
it('rejects mutually exclusive cursors and bad grades', () => {
expect(() => transcriptGradeSpecSchema.parse({ '*': 'stream' })).toThrow();
const ok = transcriptResponseSchema.safeParse({
agent_id: 'main',
items: [],
has_more: false,
tasks: [],
interactions: [],
attachments: [],
todos: [],
meta: {},
agents: [{ agentId: 'main', type: 'main' }],
pending_interactions: [],
});
expect(ok.success).toBe(true);
});
it('rejects path-hostile agent ids in the transcript query', () => {
const base = { agent_id: 'main', before_turn: undefined, after_turn: undefined, page_size: undefined };
expect(transcriptQuerySchema.safeParse({ ...base, agent_id: 'sub-1' }).success).toBe(true);
expect(transcriptQuerySchema.safeParse({ ...base, agent_id: '01HF7YAT31J7SMRT1QXGJWKR8D' }).success).toBe(true);
for (const hostile of ['../main', '..\\main', '..', 'a/b', 'a\\b', '.', 'a\0b', 'x'.repeat(200)]) {
expect(transcriptQuerySchema.safeParse({ ...base, agent_id: hostile }).success).toBe(false);
}
});
});
describe('groupMessagesIntoSnapshot (cold path)', () => {
const snapshotOf = (...parts: HistoryContentPart[]): AgentTranscriptSnapshot =>
groupMessagesIntoSnapshot([
{ role: 'user', content: parts, toolCalls: [], origin: { kind: 'user' } },
]);
it('groups flat messages into turns with folded tool results', () => {
const snapshot = groupMessagesIntoSnapshot([
{ role: 'system', content: [{ type: 'text', text: 'sys' }] },
{ role: 'user', content: [{ type: 'text', text: 'hello' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'assistant',
content: [{ type: 'think', think: 'hmm' }, { type: 'text', text: 'checking' }],
toolCalls: [{ id: 'c1', name: 'Read', arguments: '{"path":"/a"}' }],
},
{ role: 'tool', content: [{ type: 'text', text: 'file body' }], toolCallId: 'c1', toolCalls: [] },
{
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
toolCalls: [],
},
{ role: 'user', content: [{ type: 'text', text: 'next' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'user',
content: [{ type: 'text', text: 'summary of old' }],
toolCalls: [],
origin: { kind: 'compaction_summary' },
},
{ role: 'user', content: [{ type: 'text', text: 'after' }], toolCalls: [], origin: { kind: 'user' } },
]);
const kinds = snapshot.items.map((i) => i.kind);
expect(kinds).toEqual(['turn', 'turn', 'marker', 'turn']);
const firstTurn = snapshot.items[0];
if (firstTurn?.kind !== 'turn') throw new Error('expected turn');
expect(firstTurn.prompt).toBe('hello');
expect(firstTurn.steps).toHaveLength(2);
const tool = firstTurn.steps[0]?.frames.find((f) => f.kind === 'tool');
expect(tool?.kind === 'tool' && tool.output).toBe('file body');
expect(tool?.kind === 'tool' && tool.input).toEqual({ path: '/a' });
const marker = snapshot.items[2];
expect(marker?.kind === 'marker' && marker.marker).toBe('compaction');
});
it('folds task-notification user messages into the current turn instead of opening their own', () => {
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'run it' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'assistant',
content: [{ type: 'text', text: 'starting' }],
toolCalls: [{ id: 'c1', name: 'Bash', arguments: '{"command":"ls"}' }],
},
{
role: 'user',
content: [{
type: 'text',
text: '<notification id="task:task-9:completed" category="task" type="task.completed" source_kind="background_task" source_id="task-9">\nTitle: Background agent completed\nSeverity: info\ninspect done.\n</notification>',
}],
toolCalls: [],
origin: { kind: 'task', taskId: 'task-9' } as { kind: string },
},
{
role: 'user',
content: [{ type: 'text', text: '<notification id="task:task-8:completed"></notification>' }],
toolCalls: [],
origin: { kind: 'task', taskId: 'task-8' } as { kind: string },
},
{
role: 'assistant',
content: [{ type: 'text', text: 'continuing' }],
toolCalls: [],
},
],
{ taskOriginTurnTaskIds: new Set() },
);
const turns = snapshot.items.filter((i) => i.kind === 'turn');
expect(turns).toHaveLength(1);
const turn = turns[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
const userFrames = turn.steps
.flatMap((step) => step.frames)
.filter((f) => f.kind === 'text' && f.role === 'user');
expect(userFrames.map((f) => f.kind === 'text' && f.taskId)).toEqual(['task-9', 'task-8']);
expect(userFrames[0]).toMatchObject({ text: 'Background agent completed\ninspect done.' });
const assistantTexts = turn.steps
.flatMap((step) => step.frames)
.filter((f) => f.kind === 'text' && f.role === 'assistant')
.map((f) => f.kind === 'text' && f.text);
expect(assistantTexts).toEqual(['starting', 'continuing']);
expect(turn.steps).toHaveLength(2);
expect(turn.steps[1]?.frames.map((f) => f.kind === 'text' && f.role)).toEqual([
'user',
'user',
'assistant',
]);
});
it('stops folded notification text before child output blocks', () => {
const xml = [
'<notification id="task:task-9:completed" category="task" type="task.completed" source_kind="background_task" source_id="task-9">',
'Title: Background agent completed',
'Severity: info',
'inspect done.',
'<output-file>/tmp/out.log</output-file>',
'/tmp/out.log',
'full output here',
'</notification>',
].join('\n');
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'run' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'go' }], toolCalls: [] },
{ role: 'user', content: [{ type: 'text', text: xml }], toolCalls: [], origin: { kind: 'task', taskId: 'task-9' } as { kind: string } },
{ role: 'assistant', content: [{ type: 'text', text: 'done' }], toolCalls: [] },
],
{ taskOriginTurnTaskIds: new Set() },
);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
const frame = turn.steps.flatMap((step) => step.frames).find((f) => f.kind === 'text' && f.role === 'user');
expect(frame).toMatchObject({ text: 'Background agent completed\ninspect done.' });
});
it('buffers a folded notification that arrives before the first step into that step', () => {
const xml = [
'<notification id="task:task-9:completed" category="task" type="task.completed" source_kind="background_task" source_id="task-9">',
'Title: Background agent completed',
'Severity: info',
'early done.',
'</notification>',
].join('\n');
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'run' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'user', content: [{ type: 'text', text: xml }], toolCalls: [], origin: { kind: 'task', taskId: 'task-9' } as { kind: string } },
{ role: 'assistant', content: [{ type: 'text', text: 'go' }], toolCalls: [] },
],
{ taskOriginTurnTaskIds: new Set() },
);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.steps).toHaveLength(1);
expect(turn.steps[0]?.frames.map((f) => f.kind === 'text' && f.role)).toEqual([
'user',
'assistant',
]);
expect(turn.steps[0]?.frames[0]).toMatchObject({
text: 'Background agent completed\nearly done.',
taskId: 'task-9',
});
});
it('drops a folded notification when no turn is open yet', () => {
const snapshot = groupMessagesIntoSnapshot(
[
{
role: 'user',
content: [{ type: 'text', text: '<notification id="n1"></notification>' }],
toolCalls: [],
origin: { kind: 'task', taskId: 'task-9' } as { kind: string },
},
],
{ taskOriginTurnTaskIds: new Set() },
);
expect(snapshot.items).toHaveLength(0);
});
it('keeps body lines that start with an angle bracket but are not output blocks', () => {
const xml = [
'<notification id="task:task-9:completed" category="task" type="task.completed" source_kind="background_task" source_id="task-9">',
'Title: Background agent completed',
'Severity: info',
'first line.',
'<div>pasted markup</div>',
'last line.',
'</notification>',
].join('\n');
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'run' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'go' }], toolCalls: [] },
{ role: 'user', content: [{ type: 'text', text: xml }], toolCalls: [], origin: { kind: 'task', taskId: 'task-9' } as { kind: string } },
{ role: 'assistant', content: [{ type: 'text', text: 'done' }], toolCalls: [] },
],
{ taskOriginTurnTaskIds: new Set() },
);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
const frame = turn.steps.flatMap((step) => step.frames).find((f) => f.kind === 'text' && f.role === 'user');
expect(frame).toMatchObject({
text: 'Background agent completed\nfirst line.\n<div>pasted markup</div>\nlast line.',
});
});
it('parses only the leading header lines, keeping later Title-like lines as body', () => {
const xml = [
'<notification id="task:task-9:completed" category="task" type="task.completed" source_kind="background_task" source_id="task-9">',
'Title: Background agent completed',
'Severity: info',
'first line.',
'Title: details',
'last line.',
'</notification>',
].join('\n');
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'run' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'go' }], toolCalls: [] },
{ role: 'user', content: [{ type: 'text', text: xml }], toolCalls: [], origin: { kind: 'task', taskId: 'task-9' } as { kind: string } },
{ role: 'assistant', content: [{ type: 'text', text: 'done' }], toolCalls: [] },
],
{ taskOriginTurnTaskIds: new Set() },
);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
const frame = turn.steps.flatMap((step) => step.frames).find((f) => f.kind === 'text' && f.role === 'user');
expect(frame).toMatchObject({
text: 'Background agent completed\nfirst line.\nTitle: details\nlast line.',
});
});
it('expands a bundled prompt into per-skill markers and a caller-text turn', () => {
const snapshot = groupMessagesIntoSnapshot([
{
role: 'user',
content: [
{ type: 'text', text: 'rendered review block' },
{ type: 'text', text: 'rendered security block' },
{ type: 'text', text: 'please /skill:review and /skill:security' },
],
toolCalls: [],
origin: {
kind: 'user',
skillActivations: [
{ activationId: 'act-1', skillName: 'review' },
{ activationId: 'act-2', skillName: 'security', skillArgs: 'src/app.ts' },
],
} as { kind: string },
},
{ role: 'assistant', content: [{ type: 'text', text: 'done' }], toolCalls: [] },
]);
expect(snapshot.items.map((item) => item.kind)).toEqual(['marker', 'marker', 'turn']);
const first = snapshot.items[0];
expect(first?.kind === 'marker' && first.marker).toBe('skill');
expect(first?.kind === 'marker' && first.payload).toMatchObject({
text: 'rendered review block',
origin: { kind: 'skill_activation', trigger: 'user-slash', skillName: 'review' },
});
const second = snapshot.items[1];
expect(second?.kind === 'marker' && second.payload).toMatchObject({
text: 'rendered security block',
origin: { skillName: 'security', skillArgs: 'src/app.ts' },
});
const turn = snapshot.items[2];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.prompt).toBe('please /skill:review and /skill:security');
expect(turn.steps).toHaveLength(1);
});
it('maps media parts on the opening user message to attachment entities, dropping base64 bytes', () => {
const snapshot = groupMessagesIntoSnapshot([
{
role: 'user',
content: [
{ type: 'text', text: 'what is this? [Image #1]' },
{ type: 'image', source: { kind: 'base64', media_type: 'image/png', data: 'aGVsbG8=' } },
{ type: 'image', source: { kind: 'url', url: 'https://example.com/pic.png' } },
{ type: 'file', file_id: 'file_9', name: 'notes.txt', media_type: 'text/plain', size: 128 },
],
toolCalls: [],
origin: { kind: 'user' },
},
{ role: 'assistant', content: [{ type: 'text', text: 'a screenshot' }], toolCalls: [] },
]);
expect(snapshot.attachments).toHaveLength(3);
expect(snapshot.attachments[0]).toMatchObject({
attachmentId: 'att_1',
mediaType: 'image/png',
source: undefined,
});
expect(snapshot.attachments[1]).toMatchObject({
attachmentId: 'att_2',
source: { kind: 'url', url: 'https://example.com/pic.png' },
});
expect(snapshot.attachments[2]).toMatchObject({
attachmentId: 'att_3',
mediaType: 'text/plain',
name: 'notes.txt',
source: { kind: 'file', fileId: 'file_9' },
});
const firstTurn = snapshot.items[0];
if (firstTurn?.kind !== 'turn') throw new Error('expected turn');
expect(firstTurn.attachmentIds).toEqual(['att_1', 'att_2', 'att_3']);
});
it('maps persisted kimi-file media refs to attachments', () => {
const snapshot = groupMessagesIntoSnapshot([
{
role: 'user',
content: [
{
type: 'video_url',
videoUrl: { url: 'kimi-file://file_1' },
} as HistoryContentPart,
],
toolCalls: [],
origin: { kind: 'user' },
},
{
role: 'user',
content: [
{ type: 'text', text: 'what is this?' },
{
type: 'image_url',
imageUrl: { url: 'kimi-file://file_2?path=%2Fcache%2Fshot.png' },
} as HistoryContentPart,
],
toolCalls: [],
origin: { kind: 'user' },
},
]);
expect(snapshot.attachments).toEqual([
{
attachmentId: 'att_1',
mediaType: 'video/*',
source: { kind: 'session_media', fileId: 'file_1' },
},
{
attachmentId: 'att_2',
mediaType: 'image/*',
source: { kind: 'session_media', fileId: 'file_2' },
},
]);
const videoTurn = snapshot.items[0];
const imageTurn = snapshot.items[1];
if (videoTurn?.kind !== 'turn' || imageTurn?.kind !== 'turn') {
throw new Error('expected turns');
}
expect(videoTurn.attachmentIds).toEqual(['att_1']);
expect(imageTurn.attachmentIds).toEqual(['att_2']);
expect(videoTurn.prompt).toBe('');
expect(imageTurn.prompt).toBe('what is this?');
});
it('projects a daemon ref in a user-slash turn-opening input like a plain user turn', () => {
const snapshot = groupMessagesIntoSnapshot([
{
role: 'user',
content: [
{ type: 'text', text: '/gen-docs ' },
{
type: 'image_url',
imageUrl: { url: 'kimi-file://file_4?path=%2Fcache%2Fshot.png' },
} as HistoryContentPart,
],
toolCalls: [],
origin: { kind: 'skill_activation', trigger: 'user-slash', skillName: 'gen-docs' } as {
kind: string;
},
},
]);
expect(snapshot.attachments).toEqual([
{
attachmentId: 'att_1',
mediaType: 'image/*',
source: { kind: 'session_media', fileId: 'file_4' },
},
]);
expect(snapshot.items.map((item) => item.kind)).toEqual(['marker', 'turn']);
const marker = snapshot.items[0];
if (marker?.kind !== 'marker') throw new Error('expected marker');
expect((marker.payload as { text: string }).text).toBe('/gen-docs ');
const turn = snapshot.items[1];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.prompt).toBe('/gen-docs ');
expect(turn.attachmentIds).toEqual(['att_1']);
});
it('keeps a kimi-file ref as a nameless attachment and inline tags as user text', () => {
const snapshot = snapshotOf(
{ type: 'text', text: 'open <image path="/tmp/other.png"></image> please' },
{
type: 'image_url',
imageUrl: { url: 'kimi-file://file_3' },
} as HistoryContentPart,
);
expect(snapshot.attachments).toEqual([
{
attachmentId: 'att_1',
mediaType: 'image/*',
source: { kind: 'session_media', fileId: 'file_3' },
},
]);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.attachmentIds).toEqual(['att_1']);
expect(turn.prompt).toBe('open <image path="/tmp/other.png"></image> please');
});
it('keeps a legacy tag+ref pair as prompt text plus the ref-derived attachment', () => {
const snapshot = snapshotOf(
{ type: 'text', text: '<image path="/cache/shot.png"></image>' },
{
type: 'image_url',
imageUrl: { url: 'kimi-file://file_5?path=%2Fcache%2Fshot.png' },
} as HistoryContentPart,
);
expect(snapshot.attachments).toEqual([
{
attachmentId: 'att_1',
mediaType: 'image/*',
source: { kind: 'session_media', fileId: 'file_5' },
},
]);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.prompt).toBe('<image path="/cache/shot.png"></image>');
expect(turn.attachmentIds).toEqual(['att_1']);
});
it('keeps standalone <media path> tags as prompt text', () => {
const snapshot = snapshotOf(
{ type: 'text', text: '<image path="/cache/shot.png">' },
{ type: 'text', text: '<image path="/cache/shot.png" content_type="image/png"></image>' },
{ type: 'text', text: '<video path="/cache/clip.mp4">' },
);
const turn = snapshot.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.prompt).toBe(
'<image path="/cache/shot.png"><image path="/cache/shot.png" content_type="image/png"></image><video path="/cache/clip.mp4">',
);
expect(turn.attachmentIds).toBeUndefined();
expect(snapshot.attachments).toEqual([]);
});
it('keeps cold tool calls running until a result is persisted', () => {
const pending = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'run it' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [], toolCalls: [{ id: 'c1', name: 'Bash', arguments: '{}' }] },
]);
const pendingTurn = pending.items[0];
if (pendingTurn?.kind !== 'turn') throw new Error('expected turn');
const pendingTool = pendingTurn.steps[0]?.frames.find((f) => f.kind === 'tool');
expect(pendingTool?.kind === 'tool' && pendingTool.state).toBe('running');
const done = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'run it' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [], toolCalls: [{ id: 'c1', name: 'Bash', arguments: '{}' }] },
{ role: 'tool', content: [{ type: 'text', text: 'done.txt' }], toolCallId: 'c1', toolCalls: [] },
]);
const doneTurn = done.items[0];
if (doneTurn?.kind !== 'turn') throw new Error('expected turn');
const doneTool = doneTurn.steps[0]?.frames.find((f) => f.kind === 'tool');
expect(doneTool?.kind === 'tool' && doneTool.state).toBe('done');
expect(doneTool?.kind === 'tool' && doneTool.output).toBe('done.txt');
});
it('opens a turn for subagent run prompts recorded as system triggers', () => {
const snapshot = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'scan the repo' }],
toolCalls: [],
origin: { kind: 'system_trigger', name: 'subagent' } as { kind: string },
},
{ role: 'assistant', content: [{ type: 'text', text: 'scanning' }], toolCalls: [] },
]);
expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'turn']);
const subTurn = snapshot.items[1];
if (subTurn?.kind !== 'turn') throw new Error('expected turn');
expect(subTurn.ordinal).toBe(1);
expect(subTurn.origin.kind).toBe('other');
expect(subTurn.prompt).toBeUndefined();
expect(subTurn.steps).toHaveLength(1);
});
it('starts a new turn for user-slash skill activations, keeps other triggers as markers only', () => {
const snapshot = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'skill body' }],
toolCalls: [],
origin: { kind: 'skill_activation', trigger: 'user-slash', skillName: 'gen-docs' } as {
kind: string;
},
},
{ role: 'assistant', content: [{ type: 'text', text: 'docs done' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'nested skill body' }],
toolCalls: [],
origin: { kind: 'skill_activation', trigger: 'model-tool', skillName: 'x' } as {
kind: string;
},
},
{ role: 'assistant', content: [{ type: 'text', text: 'still same turn' }], toolCalls: [] },
]);
expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'marker', 'turn', 'marker']);
const slashTurn = snapshot.items[2];
if (slashTurn?.kind !== 'turn') throw new Error('expected turn');
expect(slashTurn.ordinal).toBe(1);
expect(slashTurn.origin.kind).toBe('other');
expect(slashTurn.prompt).toBe('skill body');
expect(slashTurn.steps).toHaveLength(2);
});
it('starts a promptless turn for turn-opening system triggers (goal continuation)', () => {
const snapshot = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'continue the goal' }],
toolCalls: [],
origin: { kind: 'system_trigger', name: 'goal_continuation' } as { kind: string },
},
{ role: 'assistant', content: [{ type: 'text', text: 'continued' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'mode reminder' }],
toolCalls: [],
origin: { kind: 'injection', variant: 'permission_mode' } as { kind: string },
},
{ role: 'assistant', content: [{ type: 'text', text: 'still same turn' }], toolCalls: [] },
]);
expect(snapshot.items.map((item) => item.kind)).toEqual(['turn', 'turn']);
const [first, second] = snapshot.items;
if (first?.kind !== 'turn' || second?.kind !== 'turn') throw new Error('expected turns');
expect(first.ordinal).toBe(0);
expect(first.steps.map((step) => step.frames.map((frame) => frame.kind))).toEqual([['text']]);
expect(second.ordinal).toBe(1);
expect(second.origin.kind).toBe('other');
expect(second.prompt).toBeUndefined();
expect(second.steps).toHaveLength(2);
});
it('hides injected user messages and maps cron origins', () => {
const snapshot = groupMessagesIntoSnapshot([
{
role: 'user',
content: [{ type: 'text', text: 'secret context' }],
toolCalls: [],
origin: { kind: 'injection' },
},
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'user',
content: [{ type: 'text', text: 'run report' }],
toolCalls: [],
origin: { kind: 'cron_job', jobId: 'job1' } as { kind: string },
},
]);
expect(snapshot.items).toHaveLength(2);
const cronTurn = snapshot.items[1];
expect(cronTurn?.kind === 'turn' && cronTurn.origin.kind).toBe('cron');
});
it('opens a task turn for a notification with a task-origin turn.started in the wire', () => {
const snapshot = groupMessagesIntoSnapshot(
[
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'assistant',
content: [{ type: 'text', text: 'answer' }],
toolCalls: [],
},
{
role: 'user',
content: [{ type: 'text', text: 'task done' }],
toolCalls: [],
origin: { kind: 'task', taskId: 'task-9' } as { kind: string },
},
],
{ taskOriginTurnTaskIds: new Set(['task-9']) },
);
const taskTurn = snapshot.items[1];
if (taskTurn?.kind !== 'turn') throw new Error('expected turn');
expect(taskTurn.origin).toMatchObject({ kind: 'task', taskId: 'task-9' });
});
it('maps legacy background_task origins to task turns, preserving the taskId', () => {
const snapshot = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{
role: 'user',
content: [{ type: 'text', text: 'task done' }],
toolCalls: [],
origin: { kind: 'background_task', taskId: 'b83rhswvs' } as { kind: string },
},
]);
const taskTurn = snapshot.items[1];
if (taskTurn?.kind !== 'turn') throw new Error('expected turn');
expect(taskTurn.origin).toMatchObject({ kind: 'task', taskId: 'b83rhswvs' });
});
});
describe('foldWireRecordFacts (cold facts)', () => {
const baseWithMarker = (): AgentTranscriptSnapshot =>
groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'hi' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'answer' }], toolCalls: [] },
{
role: 'user',
content: [{ type: 'text', text: 'summary of old' }],
toolCalls: [],
origin: { kind: 'compaction_summary' },
},
]);
it('returns the base snapshot unchanged when no fact records exist (old sessions)', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'metadata', protocol_version: '1.5', created_at: 1 },
{ type: 'context.append_message', message: { role: 'user' }, time: 1 },
],
base,
);
expect(folded).toEqual(base);
expect(folded.items).toBe(base.items);
});
it('folds todo records into the global todo document, last write wins', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'tools.update_store', key: 'todo', value: [{ title: 'old', status: 'pending' }], time: 1000 },
{ type: 'tools.update_store', key: 'other', value: [{ title: 'ignored', status: 'done' }], time: 2000 },
{
type: 'tools.update_store',
key: 'todo',
value: [
{ title: 'write tests', status: 'in_progress' },
{ title: 'ship', status: 'pending' },
{ title: 'malformed' },
],
time: 3000,
},
],
base,
);
expect(folded.todos).toEqual([
{
todoId: 'todo',
items: [
{ title: 'write tests', status: 'in_progress' },
{ title: 'ship', status: 'pending' },
],
updatedAt: new Date(3000).toISOString(),
},
]);
expect(folded.items).toBe(base.items);
});
it('folds goal create/update/clear into meta.goal with markers, last write wins', () => {
const base = baseWithMarker();
expect(base.items.some((item) => item.kind === 'marker' && item.markerId === 'm1')).toBe(true);
const folded = foldWireRecordFacts(
[
{
type: 'goal.create',
goalId: 'g1',
objective: 'fix the bug',
completionCriterion: 'tests pass',
time: 1000,
},
{ type: 'goal.update', status: 'blocked', reason: 'stuck', tokensUsed: 1200, time: 2000 },
{ type: 'goal.update', budgetLimits: { tokenBudget: 50000 }, time: 3000 },
],
base,
);
expect(folded.meta.goal).toEqual({
objective: 'fix the bug',
status: 'blocked',
completionCriterion: 'tests pass',
budgetUsed: 1200,
budgetLimit: 50000,
});
const goalMarkers = folded.items.filter(
(item) => item.kind === 'marker' && item.marker === 'goal',
);
expect(goalMarkers.map((item) => item.kind === 'marker' && item.markerId)).toEqual([
'm2',
'm3',
'm4',
]);
expect(goalMarkers[0]).toMatchObject({ at: new Date(1000).toISOString() });
expect(folded.items.slice(0, base.items.length)).toEqual(base.items);
const cleared = foldWireRecordFacts(
[
{ type: 'goal.create', goalId: 'g1', objective: 'fix the bug', time: 1000 },
{ type: 'goal.clear', time: 2000 },
],
base,
);
expect(cleared.meta.goal).toBeUndefined();
});
it('marks user-cancelled turns with interruption markers, skipping unattributable cancels', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'turn.cancel', turnId: 0, target: 'active', reason: 'user_cancelled', time: 1000 },
{ type: 'turn.cancel', turnId: 0, target: 'active', reason: 'user_cancelled', time: 1500 },
{ type: 'turn.cancel', turnId: 1, target: 'queued', reason: 'user_cancelled', time: 2000 },
{ type: 'turn.cancel', turnId: 2, target: 'active', reason: 'aborted', time: 3000 },
{ type: 'turn.cancel', turnId: 4, target: 'active', reason: 'aborted', time: 3500 },
{ type: 'turn.cancel', turnId: 4, target: 'active', reason: 'user_cancelled', time: 3600 },
{ type: 'turn.cancel', turnId: 3, target: 'active', time: 4000 },
],
base,
);
expect(
folded.items.filter((item) => item.kind === 'marker' && item.marker === 'interruption'),
).toEqual([
{
kind: 'marker',
markerId: 'm2',
marker: 'interruption',
payload: { turnId: 0, target: 'active', reason: 'user_cancelled' },
at: new Date(1000).toISOString(),
},
]);
});
it('folds plan/swarm mode records into meta.modes with enter/exit markers', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 },
{ type: 'swarm_mode.enter', trigger: { kind: 'task' }, time: 2000 },
{ type: 'plan_mode.exit', id: 'plan-1', time: 3000 },
],
base,
);
expect(folded.meta.modes).toEqual({ swarm: {} });
const markers = folded.items
.filter((item) => item.kind === 'marker')
.map((item) => item.kind === 'marker' && item.marker);
expect(markers).toEqual(['compaction', 'plan.enter', 'swarm.enter', 'plan.exit']);
const cancelled = foldWireRecordFacts(
[
{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 },
{ type: 'plan_mode.cancel', time: 2000 },
{ type: 'swarm_mode.enter', trigger: { kind: 'tool' }, time: 3000 },
{ type: 'swarm_mode.exit', time: 4000 },
],
base,
);
expect(cancelled.meta.modes).toEqual({});
const stillPlanning = foldWireRecordFacts(
[{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 }],
base,
);
expect(stillPlanning.meta.modes).toEqual({ plan: {} });
});
it('folds plan.revision records into the plan badge and a timeline marker', () => {
const base = baseWithMarker();
const revision = {
type: 'plan.revision',
id: 'plan-1',
version: 2,
path: 'agents/main/plan/plan-1/v2.md',
sha256: 'deadbeef',
bytes: 512,
time: 2000,
};
const folded = foldWireRecordFacts(
[{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 }, revision],
base,
);
expect(folded.meta.modes).toEqual({
plan: { reviewPath: 'agents/main/plan/plan-1/v2.md', version: 2 },
});
const revisionMarkers = folded.items.filter(
(item) => item.kind === 'marker' && item.marker === 'plan.revision',
);
expect(revisionMarkers).toEqual([
{
kind: 'marker',
markerId: 'm3',
marker: 'plan.revision',
payload: {
id: 'plan-1',
version: 2,
path: 'agents/main/plan/plan-1/v2.md',
sha256: 'deadbeef',
bytes: 512,
},
at: new Date(2000).toISOString(),
},
]);
const exited = foldWireRecordFacts(
[
{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 },
revision,
{ type: 'plan_mode.exit', id: 'plan-1', time: 3000 },
],
base,
);
expect(exited.meta.modes?.plan).toBeUndefined();
expect(
exited.items.filter((item) => item.kind === 'marker' && item.marker === 'plan.revision'),
).toHaveLength(1);
const reentered = foldWireRecordFacts(
[
{ type: 'plan_mode.enter', id: 'plan-1', time: 1000 },
revision,
{ type: 'plan_mode.exit', id: 'plan-1', time: 3000 },
{ type: 'plan_mode.enter', id: 'plan-2', time: 4000 },
],
base,
);
expect(reentered.meta.modes).toEqual({ plan: {} });
});
it('folds task records into task entities and timeline taskrefs', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{
type: 'task.started',
info: {
taskId: 'task_1',
kind: 'process',
description: 'pnpm test',
status: 'running',
startedAt: 1000,
endedAt: null,
},
time: 1000,
},
{
type: 'task.started',
info: {
taskId: 'task_2',
kind: 'agent',
description: 'scan the repo',
status: 'running',
detached: false,
agentId: 'sub-1',
startedAt: 2000,
endedAt: null,
},
time: 2000,
},
{
type: 'task.terminated',
info: {
taskId: 'task_1',
kind: 'process',
description: 'pnpm test',
status: 'completed',
startedAt: 1000,
endedAt: 5000,
},
outputTail: '42 passed',
time: 5000,
},
],
base,
);
expect(folded.tasks).toEqual([
{
taskId: 'task_1',
kind: 'shell',
state: 'completed',
detached: true,
description: 'pnpm test',
agentId: undefined,
outputTail: '42 passed',
startedAt: new Date(1000).toISOString(),
endedAt: new Date(5000).toISOString(),
},
{
taskId: 'task_2',
kind: 'subagent',
state: 'running',
detached: false,
description: 'scan the repo',
agentId: 'sub-1',
outputTail: '',
startedAt: new Date(2000).toISOString(),
endedAt: undefined,
},
]);
const refs = folded.items.filter((item) => item.kind === 'taskref');
expect(refs).toEqual([
{ kind: 'taskref', refId: 'ref-task_1', taskId: 'task_1', at: new Date(1000).toISOString() },
{ kind: 'taskref', refId: 'ref-task_2', taskId: 'task_2', at: new Date(2000).toISOString() },
]);
});
it('maps unknown task kinds to other and survives malformed task records', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{
type: 'task.started',
info: { taskId: 'task_q', kind: 'question', status: 'running', startedAt: 1000, endedAt: null },
time: 1000,
},
{ type: 'task.started', time: 2000 },
{ type: 'task.terminated', info: { kind: 'process' }, time: 3000 },
],
base,
);
expect(folded.tasks).toHaveLength(1);
expect(folded.tasks[0]).toMatchObject({ taskId: 'task_q', kind: 'other', state: 'running' });
});
it('folds interaction request/resolved into entities with terminal states', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{
type: 'interaction.request',
id: 'apr-1',
kind: 'approval',
toolCallId: 'call_1',
request: { toolName: 'Bash' },
time: 1000,
},
{
type: 'interaction.request',
id: 'q-1',
kind: 'question',
request: { questions: [] },
time: 2000,
},
{
type: 'interaction.resolved',
id: 'apr-1',
response: { decision: 'approved', scope: 'session' },
time: 3000,
},
{ type: 'interaction.resolved', id: 'q-1', response: null, time: 4000 },
],
base,
);
expect(folded.interactions).toEqual([
{
interactionId: 'apr-1',
interactionKind: 'approval',
toolCallId: 'call_1',
state: 'approved',
request: { toolName: 'Bash' },
response: { decision: 'approved', scope: 'session' },
},
{
interactionId: 'q-1',
interactionKind: 'question',
toolCallId: undefined,
state: 'dismissed',
request: { questions: [] },
response: null,
},
]);
});
it('cancels interactions still pending at the end of the scan (crash == cancelled)', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{
type: 'interaction.request',
id: 'apr-9',
kind: 'approval',
request: { toolCallId: 'call_9', toolName: 'Write' },
time: 1000,
},
],
base,
);
expect(folded.interactions).toHaveLength(1);
expect(folded.interactions[0]).toMatchObject({
interactionId: 'apr-9',
state: 'cancelled',
toolCallId: 'call_9',
});
expect(folded.interactions.every((entity) => entity.state !== 'pending')).toBe(true);
});
it('skips user_tool interactions like the live path', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'interaction.request', id: 'ut-1', kind: 'user_tool', request: {}, time: 1000 },
{ type: 'interaction.resolved', id: 'ut-1', response: {}, time: 2000 },
] satisfies HistoryWireRecord[],
base,
);
expect(folded.interactions).toEqual([]);
});
it('folds turn.ended records into the matching turn items', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{
type: 'turn.ended',
turnId: 0,
reason: 'failed',
error: { code: 'provider.overloaded', message: 'Overloaded', name: 'APIStatusError', retryable: true },
durationMs: 1234,
time: 5000,
},
],
base,
);
const turn = folded.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.state).toBe('failed');
expect(turn.error).toBe('Overloaded');
expect(turn.durationMs).toBe(1234);
expect(turn.endedAt).toBe(new Date(5000).toISOString());
expect(folded.items).toHaveLength(base.items.length);
});
it('applies turn.ended records last-wins per turn and folds blocked into failed', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'turn.ended', turnId: 0, reason: 'failed', error: { message: 'boom' }, time: 1000 },
{ type: 'turn.ended', turnId: 0, reason: 'cancelled', durationMs: 10, time: 2000 },
],
base,
);
const turn = folded.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.state).toBe('cancelled');
expect(turn.error).toBeUndefined();
expect(turn.durationMs).toBe(10);
expect(turn.endedAt).toBe(new Date(2000).toISOString());
const blocked = foldWireRecordFacts(
[{ type: 'turn.ended', turnId: 0, reason: 'blocked', time: 3000 }],
base,
);
const blockedTurn = blocked.items[0];
if (blockedTurn?.kind !== 'turn') throw new Error('expected turn');
expect(blockedTurn.state).toBe('failed');
});
it('ignores turn.ended records with unknown turn ids or malformed payloads', () => {
const base = baseWithMarker();
const folded = foldWireRecordFacts(
[
{ type: 'turn.ended', turnId: 9, reason: 'cancelled', time: 1000 },
{ type: 'turn.ended', reason: 'completed', time: 2000 },
{ type: 'turn.ended', turnId: 0, reason: 'not-a-reason', time: 3000 },
],
base,
);
const turn = folded.items[0];
if (turn?.kind !== 'turn') throw new Error('expected turn');
expect(turn.state).toBe('completed');
expect(turn.endedAt).toBe(new Date(3000).toISOString());
expect(folded.items).toHaveLength(base.items.length);
});
it('maps turn.ended around hidden retry turns replayed from the turn-clock records', () => {
const base = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'one' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'a1' }], toolCalls: [] },
{ role: 'user', content: [{ type: 'text', text: 'two' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'a2' }], toolCalls: [] },
]);
const folded = foldWireRecordFacts(
[
{ type: 'turn.prompt', input: [{ type: 'text', text: 'one' }], origin: { kind: 'user' }, time: 1 },
{ type: 'turn.ended', turnId: 0, reason: 'completed', time: 2 },
{ type: 'turn.prompt', input: [], origin: { kind: 'retry' }, time: 3 },
{ type: 'turn.ended', turnId: 1, reason: 'failed', error: { message: 'retry boom' }, time: 4 },
{ type: 'turn.prompt', input: [{ type: 'text', text: 'two' }], origin: { kind: 'user' }, time: 5 },
{ type: 'turn.ended', turnId: 2, reason: 'cancelled', durationMs: 20, time: 6 },
],
base,
);
const first = folded.items[0];
if (first?.kind !== 'turn') throw new Error('expected turn');
expect(first.state).toBe('completed');
const second = folded.items[1];
if (second?.kind !== 'turn') throw new Error('expected turn');
expect(second.state).toBe('cancelled');
expect(second.error).toBeUndefined();
expect(second.durationMs).toBe(20);
});
it('maps turn.ended across queued-then-cancelled turn reservations', () => {
const base = groupMessagesIntoSnapshot([
{ role: 'user', content: [{ type: 'text', text: 'one' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'a1' }], toolCalls: [] },
{ role: 'user', content: [{ type: 'text', text: 'two' }], toolCalls: [], origin: { kind: 'user' } },
{ role: 'assistant', content: [{ type: 'text', text: 'a2' }], toolCalls: [] },
]);
const folded = foldWireRecordFacts(
[
{ type: 'turn.prompt', input: [{ type: 'text', text: 'one' }], origin: { kind: 'user' }, time: 1 },
{ type: 'turn.ended', turnId: 0, reason: 'completed', time: 2 },
{ type: 'turn.cancel', turnId: 1, target: 'queued', time: 3 },
{ type: 'turn.prompt', input: [{ type: 'text', text: 'two' }], origin: { kind: 'user' }, time: 4 },
{ type: 'turn.ended', turnId: 2, reason: 'failed', error: { message: 'boom' }, time: 5 },
],
base,
);
const second = folded.items[1];
if (second?.kind !== 'turn') throw new Error('expected turn');
expect(second.state).toBe('failed');
expect(second.error).toBe('boom');
});
});