import type { TranscriptInteraction } from '../model/interaction'; import type { TranscriptItem, TranscriptMarker, TranscriptTaskRef } from '../model/item'; import type { GoalMeta, GoalStatus, TranscriptMeta } from '../model/meta'; import type { TranscriptTask } from '../model/task'; import type { TodoItem, TranscriptTodo } from '../model/todo'; import type { TranscriptTurn } from '../model/turn'; import type { AgentTranscriptSnapshot } from '../ops/operation'; export interface HistoryWireRecord { readonly type: string; readonly time?: number; readonly [key: string]: unknown; } interface UpdateStorePayload { readonly key?: unknown; readonly value?: unknown; } interface GoalPayload { readonly objective?: unknown; readonly completionCriterion?: unknown; readonly status?: unknown; readonly tokensUsed?: unknown; readonly budgetLimits?: { readonly tokenBudget?: unknown }; } interface TaskInfoPayload { readonly taskId?: unknown; readonly kind?: unknown; readonly status?: unknown; readonly detached?: unknown; readonly description?: unknown; readonly agentId?: unknown; readonly startedAt?: unknown; readonly endedAt?: unknown; } interface InteractionRequestPayload { readonly id?: unknown; readonly kind?: unknown; readonly toolCallId?: unknown; readonly request?: unknown; } interface InteractionResolvedPayload { readonly id?: unknown; readonly response?: unknown; } interface PlanRevisionPayload { readonly id?: unknown; readonly version?: unknown; readonly key?: unknown; readonly path?: unknown; readonly sha256?: unknown; readonly bytes?: unknown; } interface TurnEndedPayload { readonly turnId?: unknown; readonly reason?: unknown; readonly error?: unknown; readonly durationMs?: unknown; } interface TurnStepInterruptedPayload { readonly turnId?: unknown; readonly step?: unknown; readonly reason?: unknown; readonly message?: unknown; } interface TurnPromptPayload { readonly origin?: unknown; readonly promptId?: unknown; readonly turnId?: unknown; } interface ContextUndoPayload { readonly count?: unknown; } interface ContextAppendMessagePayload { readonly message?: { readonly id?: unknown; readonly role?: unknown; readonly origin?: unknown; }; } interface TurnCancelPayload { readonly turnId?: unknown; readonly target?: unknown; readonly reason?: unknown; } function isVisibleTurnOrigin(origin: unknown): boolean { const kind = (origin as { kind?: unknown } | undefined)?.kind; if (kind === 'system_trigger') { const name = (origin as { name?: unknown } | undefined)?.name; return name === 'goal_continuation' || name === 'subagent'; } if (kind === 'skill_activation' || kind === 'plugin_command') { return (origin as { trigger?: unknown } | undefined)?.trigger === 'user-slash'; } if (kind === 'injection' || kind === 'retry' || kind === 'compaction_summary') return false; return true; } function isUndoAnchorTurnOrigin(origin: unknown): boolean { const payload = origin as { kind?: unknown; trigger?: unknown } | undefined; if (payload?.kind === undefined || payload.kind === 'user') return true; return ( (payload.kind === 'skill_activation' || payload.kind === 'plugin_command') && payload.trigger === 'user-slash' ); } function turnOriginKind(origin: unknown): TranscriptTurn['origin']['kind'] { const payload = origin as { kind?: unknown; taskId?: unknown } | undefined; switch (payload?.kind) { case 'user': case 'shell_command': return 'user'; case 'cron_job': case 'cron_missed': return 'cron'; case 'task': case 'background_task': return typeof payload.taskId === 'string' ? 'task' : 'other'; case 'hook_result': return 'hook'; case 'compaction_summary': return 'compaction'; default: return 'other'; } } function mapTaskKind(kind: unknown): TranscriptTask['kind'] { switch (kind) { case 'process': return 'shell'; case 'agent': return 'subagent'; default: return 'other'; } } const TASK_STATES = new Set([ 'running', 'completed', 'failed', 'timed_out', 'killed', 'lost', ]); const GOAL_STATUSES = new Set(['active', 'paused', 'blocked', 'complete']); function mapInteractionEndState( kind: TranscriptInteraction['interactionKind'], response: unknown, ): TranscriptInteraction['state'] { if (kind === 'question') return response === null ? 'dismissed' : 'answered'; const decision = (response as { decision?: unknown } | null | undefined)?.decision; if (decision === 'approved' || decision === 'rejected' || decision === 'cancelled') { return decision; } return 'cancelled'; } function mapTurnEndReason(reason: unknown): TranscriptTurn['state'] | undefined { switch (reason) { case 'completed': return 'completed'; case 'cancelled': return 'cancelled'; case 'failed': case 'blocked': return 'failed'; default: return undefined; } } function readTurnErrorMessage(error: unknown): string | undefined { if (error === null || typeof error !== 'object') return undefined; const message = (error as { message?: unknown }).message; return typeof message === 'string' ? message : undefined; } function recordTimeIso(record: HistoryWireRecord): string | undefined { const time: unknown = record.time; if (typeof time === 'number' && Number.isFinite(time)) return new Date(time).toISOString(); if (typeof time === 'string') return time; return undefined; } function epochMsToIso(value: unknown): string | undefined { return typeof value === 'number' && Number.isFinite(value) ? new Date(value).toISOString() : undefined; } function payloadOf(record: HistoryWireRecord): Record { const { type: _type, time: _time, ...payload } = record; return payload; } function readTodoItems(raw: unknown): TodoItem[] { if (!Array.isArray(raw)) return []; const items: TodoItem[] = []; for (const entry of raw) { const title = (entry as { title?: unknown } | undefined)?.title; const status = (entry as { status?: unknown } | undefined)?.status; if (typeof title !== 'string') continue; if (status !== 'pending' && status !== 'in_progress' && status !== 'done') continue; items.push({ title, status }); } return items; } export function foldWireRecordFacts( records: Iterable, base: AgentTranscriptSnapshot, options?: { readonly resolvePlanRevisionKey?: (key: string) => string }, ): AgentTranscriptSnapshot { const tasks = new Map(); const interactions = new Map(); const endedTurns = new Map(); const interruptedSteps = new Map>(); const turnPromptIds = new Map(); const turnOrigins = new Map(); let hasExplicitTurnId = false; let nextTurnId = 0; const cancelledTurnIds = new Set(); const hiddenTurnIds = new Set(); const undoAnchors: { firstRawTurnId: number }[] = []; const pendingUndoAnchorTurnIds: number[] = []; let undoAnchorFloor = 0; const skipCancelledTurnIds = (): void => { while (cancelledTurnIds.delete(nextTurnId)) { hiddenTurnIds.add(nextTurnId); nextTurnId += 1; } }; let todo: TranscriptTodo | undefined; let goal: GoalMeta | undefined; let goalTouched = false; let planActive: boolean | undefined; let planRevision: { readonly reviewPath?: string; readonly version?: number } | undefined; let swarmActive: boolean | undefined; let towerActive: boolean | undefined; const appended: TranscriptItem[] = []; const activeCancelTurnIds = new Set(); let markerSeq = 0; const usedRefIds = new Set(); for (const item of base.items) { if (item.kind === 'marker') { const match = /^m(\d+)$/.exec(item.markerId); if (match !== null) markerSeq = Math.max(markerSeq, Number(match[1])); } else if (item.kind === 'taskref') { usedRefIds.add(item.refId); } } const pushMarker = (marker: string, record: HistoryWireRecord): void => { markerSeq += 1; const item: TranscriptMarker = { kind: 'marker', markerId: `m${markerSeq}`, marker, payload: payloadOf(record), at: recordTimeIso(record), }; appended.push(item); }; const upsertTask = (record: HistoryWireRecord): void => { const info = record['info'] as TaskInfoPayload | undefined; if (info === undefined || typeof info.taskId !== 'string') return; const taskId = info.taskId; const prev = tasks.get(taskId); const status = info.status; const task: TranscriptTask = { taskId, kind: mapTaskKind(info.kind), state: typeof status === 'string' && TASK_STATES.has(status as TranscriptTask['state']) ? (status as TranscriptTask['state']) : (prev?.state ?? 'running'), detached: typeof info.detached === 'boolean' ? info.detached : (prev?.detached ?? true), description: typeof info.description === 'string' ? info.description : prev?.description, agentId: typeof info.agentId === 'string' ? info.agentId : prev?.agentId, outputTail: typeof record['outputTail'] === 'string' ? record['outputTail'] : (prev?.outputTail ?? ''), startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt), endedAt: epochMsToIso(info.endedAt) ?? prev?.endedAt, }; tasks.set(taskId, task); if (record.type === 'task.started') { const refId = `ref-${taskId}`; if (!usedRefIds.has(refId)) { usedRefIds.add(refId); const ref: TranscriptTaskRef = { kind: 'taskref', refId, taskId, at: recordTimeIso(record), }; appended.push(ref); } } }; for (const record of records) { switch (record.type) { case 'tools.update_store': { const payload = record as UpdateStorePayload; if (payload.key !== 'todo') break; todo = { todoId: 'todo', items: readTodoItems(payload.value), updatedAt: recordTimeIso(record), }; break; } case 'goal.create': { const payload = record as GoalPayload; goalTouched = true; goal = { objective: typeof payload.objective === 'string' ? payload.objective : '', status: 'active', completionCriterion: typeof payload.completionCriterion === 'string' ? payload.completionCriterion : undefined, budgetUsed: 0, }; pushMarker('goal', record); break; } case 'goal.update': { const payload = record as GoalPayload; goalTouched = true; if (goal !== undefined) { const tokenBudget = payload.budgetLimits?.tokenBudget; goal = { ...goal, status: typeof payload.status === 'string' && GOAL_STATUSES.has(payload.status as GoalStatus) ? (payload.status as GoalStatus) : goal.status, budgetUsed: typeof payload.tokensUsed === 'number' ? payload.tokensUsed : goal.budgetUsed, budgetLimit: typeof tokenBudget === 'number' ? tokenBudget : goal.budgetLimit, }; } pushMarker('goal', record); break; } case 'goal.clear': { goalTouched = true; goal = undefined; break; } case 'plan_mode.enter': { planActive = true; planRevision = undefined; pushMarker('plan.enter', record); break; } case 'plan_mode.exit': case 'plan_mode.cancel': { planActive = false; planRevision = undefined; pushMarker('plan.exit', record); break; } case 'plan.revision': { const payload = record as PlanRevisionPayload; const path = typeof payload.key === 'string' ? (options?.resolvePlanRevisionKey?.(payload.key) ?? payload.key) : typeof payload.path === 'string' ? payload.path : undefined; planActive = true; planRevision = { reviewPath: path, version: typeof payload.version === 'number' ? payload.version : undefined, }; if (path === undefined) { pushMarker('plan.revision', record); } else { const { key: _key, ...rest } = record; pushMarker('plan.revision', { ...rest, path }); } break; } case 'swarm_mode.enter': { swarmActive = true; pushMarker('swarm.enter', record); break; } case 'swarm_mode.exit': { swarmActive = false; pushMarker('swarm.exit', record); break; } case 'tower_mode.enter': { towerActive = true; break; } case 'tower_mode.exit': { towerActive = false; break; } case 'task.started': case 'task.terminated': { upsertTask(record); break; } case 'turn.cancel': { const payload = record as TurnCancelPayload; if ( payload.target === 'queued' && typeof payload.turnId === 'number' && payload.turnId >= nextTurnId ) { cancelledTurnIds.add(payload.turnId); skipCancelledTurnIds(); break; } if ( payload.target !== 'active' || typeof payload.turnId !== 'number' || !Number.isInteger(payload.turnId) || payload.turnId < 0 || activeCancelTurnIds.has(payload.turnId) ) { break; } activeCancelTurnIds.add(payload.turnId); if (payload.reason !== 'user_cancelled') break; pushMarker('interruption', record); break; } case 'context.undo': { const count = (record as ContextUndoPayload).count; if (typeof count !== 'number' || !Number.isSafeInteger(count) || count <= 0) break; let firstUndoneTurnId: number | undefined; for (let i = 0; i < count && undoAnchors.length > undoAnchorFloor; i++) { const anchor = undoAnchors.pop(); if (anchor !== undefined) firstUndoneTurnId = anchor.firstRawTurnId; } if (firstUndoneTurnId !== undefined) { for (let turnId = firstUndoneTurnId; turnId < nextTurnId; turnId++) { hiddenTurnIds.add(turnId); } } break; } case 'context.clear': case 'context.apply_compaction': { undoAnchorFloor = undoAnchors.length; break; } case 'context.append_message': { const message = (record as ContextAppendMessagePayload).message; if (message?.role !== 'user' || !isUndoAnchorTurnOrigin(message.origin)) break; const matchingIndex = typeof message.id === 'string' ? pendingUndoAnchorTurnIds.findIndex( (turnId) => turnPromptIds.get(turnId) === message.id, ) : -1; const legacyIndex = matchingIndex < 0 && typeof message.id === 'string' ? pendingUndoAnchorTurnIds.findIndex((turnId) => !turnPromptIds.has(turnId)) : -1; const matchedTurnId = matchingIndex >= 0 ? pendingUndoAnchorTurnIds.splice(matchingIndex, 1)[0] : legacyIndex >= 0 ? pendingUndoAnchorTurnIds.splice(legacyIndex, 1)[0] : typeof message.id !== 'string' ? pendingUndoAnchorTurnIds.shift() : undefined; if ( matchedTurnId !== undefined && !turnPromptIds.has(matchedTurnId) && typeof message.id === 'string' ) { turnPromptIds.set(matchedTurnId, message.id); } undoAnchors.push({ firstRawTurnId: matchedTurnId ?? nextTurnId }); break; } case 'interaction.request': { const payload = record as InteractionRequestPayload; if (payload.kind !== 'approval' && payload.kind !== 'question') break; if (typeof payload.id !== 'string') break; const requestToolCallId = (payload.request as { toolCallId?: unknown } | undefined) ?.toolCallId; const toolCallId = typeof payload.toolCallId === 'string' ? payload.toolCallId : typeof requestToolCallId === 'string' ? requestToolCallId : undefined; interactions.set(payload.id, { interactionId: payload.id, interactionKind: payload.kind, toolCallId, state: 'pending', request: payload.request, }); break; } case 'interaction.resolved': { const payload = record as InteractionResolvedPayload; if (typeof payload.id !== 'string') break; const entity = interactions.get(payload.id); if (entity === undefined) break; interactions.set(payload.id, { ...entity, state: mapInteractionEndState(entity.interactionKind, payload.response), response: payload.response, }); break; } case 'turn.ended': { const payload = record as TurnEndedPayload; if (typeof payload.turnId === 'number') { endedTurns.set(payload.turnId, record); const pendingIndex = pendingUndoAnchorTurnIds.indexOf(payload.turnId); if (pendingIndex >= 0) pendingUndoAnchorTurnIds.splice(pendingIndex, 1); } break; } case 'turn.step.interrupted': { const payload = record as TurnStepInterruptedPayload; if ( typeof payload.turnId !== 'number' || typeof payload.step !== 'number' || typeof payload.reason !== 'string' ) { break; } let steps = interruptedSteps.get(payload.turnId); if (steps === undefined) { steps = new Map(); interruptedSteps.set(payload.turnId, steps); } steps.set(payload.step, record); break; } case 'turn.prompt': { skipCancelledTurnIds(); const payload = record as TurnPromptPayload; const turnId = typeof payload.turnId === 'number' ? payload.turnId : nextTurnId; hasExplicitTurnId ||= typeof payload.turnId === 'number'; nextTurnId = Math.max(nextTurnId, turnId + 1); turnOrigins.set(turnId, payload.origin); if (typeof payload.promptId === 'string') turnPromptIds.set(turnId, payload.promptId); if (isUndoAnchorTurnOrigin(payload.origin)) pendingUndoAnchorTurnIds.push(turnId); if (!isVisibleTurnOrigin(payload.origin)) hiddenTurnIds.add(turnId); break; } default: break; } } for (const [id, entity] of interactions) { if (entity.state === 'pending') { interactions.set(id, { ...entity, state: 'cancelled' }); } } const baseTurns = base.items.filter((item): item is TranscriptTurn => item.kind === 'turn'); const ordinalByPromptId = new Map( baseTurns.flatMap((turn) => turn.triggerPromptId === undefined ? [] : [[turn.triggerPromptId, turn.ordinal] as const], ), ); const claimedOrdinals = new Set(); const ordinalByRawTurnId = new Map(); const claimBaseOrdinal = ( predicate: (turn: TranscriptTurn) => boolean = () => true, ): number | undefined => { const turn = baseTurns.find( (candidate) => !claimedOrdinals.has(candidate.ordinal) && predicate(candidate), ); if (turn === undefined) return undefined; claimedOrdinals.add(turn.ordinal); return turn.ordinal; }; const lastRawTurnId = Math.max(nextTurnId - 1, ...endedTurns.keys(), ...interruptedSteps.keys()); const rawTurnIds = Array.from( { length: lastRawTurnId + 1 }, (_, turnId) => turnId, ).filter( (turnId) => !hiddenTurnIds.has(turnId) && (!hasExplicitTurnId || turnOrigins.has(turnId) || endedTurns.has(turnId) || interruptedSteps.has(turnId)), ); for (const turnId of rawTurnIds) { const promptId = turnPromptIds.get(turnId); const matchedOrdinal = promptId === undefined ? undefined : ordinalByPromptId.get(promptId); if (matchedOrdinal !== undefined && !claimedOrdinals.has(matchedOrdinal)) { claimedOrdinals.add(matchedOrdinal); ordinalByRawTurnId.set(turnId, matchedOrdinal); } } for (const turnId of rawTurnIds) { if (ordinalByRawTurnId.has(turnId)) continue; const origin = turnOrigins.get(turnId); const strictOrigin = turnOrigins.has(turnId) && !isUndoAnchorTurnOrigin(origin); if (!strictOrigin) continue; const fallbackOrdinal = claimBaseOrdinal( (candidate) => candidate.triggerPromptId === undefined && candidate.origin.kind === turnOriginKind(origin), ); if (fallbackOrdinal !== undefined) ordinalByRawTurnId.set(turnId, fallbackOrdinal); } for (const turnId of rawTurnIds) { if (ordinalByRawTurnId.has(turnId)) continue; const promptId = turnPromptIds.get(turnId); const origin = turnOrigins.get(turnId); const strictOrigin = turnOrigins.has(turnId) && !isUndoAnchorTurnOrigin(origin); if (promptId === undefined || strictOrigin) continue; const emptyPromptOrdinal = claimBaseOrdinal( (candidate) => candidate.triggerPromptId === undefined && candidate.origin.kind === 'other', ); if (emptyPromptOrdinal !== undefined) { ordinalByRawTurnId.set(turnId, emptyPromptOrdinal); } } for (const turnId of rawTurnIds) { if (ordinalByRawTurnId.has(turnId)) continue; const promptId = turnPromptIds.get(turnId); const origin = turnOrigins.get(turnId); const strictOrigin = turnOrigins.has(turnId) && !isUndoAnchorTurnOrigin(origin); if (promptId !== undefined || strictOrigin) continue; const fallbackOrdinal = claimBaseOrdinal(); if (fallbackOrdinal !== undefined) ordinalByRawTurnId.set(turnId, fallbackOrdinal); } const endedByOrdinal = new Map(); for (const [turnId, record] of endedTurns) { const ordinal = ordinalByRawTurnId.get(turnId); if (ordinal !== undefined) endedByOrdinal.set(ordinal, record); } const interruptedByOrdinal = new Map>(); for (const [turnId, steps] of interruptedSteps) { const ordinal = ordinalByRawTurnId.get(turnId); if (ordinal !== undefined) interruptedByOrdinal.set(ordinal, steps); } const items = endedByOrdinal.size > 0 || interruptedByOrdinal.size > 0 ? base.items.map((item) => { if (item.kind !== 'turn') return item; const record = endedByOrdinal.get(item.ordinal); const interrupted = interruptedByOrdinal.get(item.ordinal); if (record === undefined && interrupted === undefined) return item; const steps = ((): TranscriptTurn['steps'] => { if (interrupted === undefined) return item.steps; const hitOrdinals = new Set(); const patched = item.steps.map((step) => { const hit = interrupted.get(step.ordinal); if (hit === undefined) return step; const stepPayload = hit as TurnStepInterruptedPayload; if (typeof stepPayload.reason !== 'string') return step; hitOrdinals.add(step.ordinal); return { ...step, state: 'interrupted' as const, endedAt: recordTimeIso(hit) ?? step.endedAt, endReason: stepPayload.reason, endMessage: typeof stepPayload.message === 'string' ? stepPayload.message : undefined, }; }); for (const [stepOrdinal, hit] of interrupted) { if (hitOrdinals.has(stepOrdinal)) continue; const stepPayload = hit as TurnStepInterruptedPayload; if (typeof stepPayload.reason !== 'string') continue; patched.push({ kind: 'step', stepId: `${item.turnId}.${stepOrdinal}`, turnId: item.turnId, ordinal: stepOrdinal, state: 'interrupted', frames: [], endedAt: recordTimeIso(hit), endReason: stepPayload.reason, endMessage: typeof stepPayload.message === 'string' ? stepPayload.message : undefined, }); } return patched.toSorted((a, b) => a.ordinal - b.ordinal); })(); if (record === undefined) return { ...item, steps }; const payload = record as TurnEndedPayload; return { ...item, steps, state: mapTurnEndReason(payload.reason) ?? item.state, endedAt: recordTimeIso(record) ?? item.endedAt, durationMs: typeof payload.durationMs === 'number' ? payload.durationMs : item.durationMs, error: readTurnErrorMessage(payload.error) ?? item.error, }; }) : base.items; const modesTouched = planActive !== undefined || swarmActive !== undefined || towerActive !== undefined; const meta: TranscriptMeta = { ...base.meta, goal: goalTouched ? goal : base.meta.goal, modes: modesTouched ? { ...base.meta.modes, plan: planActive === undefined ? base.meta.modes?.plan : planActive ? (planRevision ?? {}) : undefined, swarm: swarmActive === undefined ? base.meta.modes?.swarm : swarmActive ? {} : undefined, tower: towerActive === undefined ? base.meta.modes?.tower : towerActive ? {} : undefined, } : base.meta.modes, }; return { ...base, items: appended.length > 0 ? [...items, ...appended] : items, tasks: [...tasks.values()], interactions: [...interactions.values()], todos: todo !== undefined ? [todo] : base.todos, meta, }; }