import type { ContextSpliced } from '@moonshot-ai/agent-core-v2/agent/contextMemory/contextEvents'; import type { HookResult } from '@moonshot-ai/agent-core-v2/features/externalHooks/agent/agentExternalHooksService'; import type { CompactionBlocked, CompactionCancelled, CompactionCompleted, CompactionStarted, } from '@moonshot-ai/agent-core-v2/agent/fullCompaction/compactionOps'; import { daemonFileRefFromPart, type ContentPart, type ContextUndone, type CronFired, type GoalUpdated } from '@moonshot-ai/agent-core-v2'; import type { AssistantDelta, ThinkingDelta, ToolCallDelta, TurnStarted, TurnStepCompleted, TurnStepInterrupted, TurnStepRetrying, TurnStepStarted, } from '@moonshot-ai/agent-core-v2/agent/loop/turnEvents'; import type { TurnEnded, TurnSteer } from '@moonshot-ai/agent-core-v2/agent/loop/turnOps'; import type { AgentActivitySnapshot } from '@moonshot-ai/agent-core-v2/agent/loop/loop'; import type { AgentErrorEvent } from '@moonshot-ai/agent-core-v2/agent/mcp/mcpEvents'; import type { PluginCommandActivated } from '@moonshot-ai/agent-core-v2/agent/pluginCommand/pluginCommand'; import type { WarningIssued } from '@moonshot-ai/agent-core-v2/agent/profile/profileOps'; import type { PromptAborted, PromptCompleted, PromptQueued, PromptStarted, PromptSteered, PromptSubmitted, } from '@moonshot-ai/agent-core-v2/agent/prompt/promptEvents'; import type { ShellCompleted, ShellOutput, ShellStarted, } from '@moonshot-ai/agent-core-v2/agent/shellCommand/shellCommandService'; import type { SkillActivated } from '@moonshot-ai/agent-core-v2/features/skill/skillOps'; import type { TaskNotified, TaskStarted, TaskTerminatedNotice, } from '@moonshot-ai/agent-core-v2/agent/task/taskOps'; import type { PermissionApprovalRequested, PermissionApprovalResolved, } from '@moonshot-ai/agent-core-v2/agent/toolApproval/toolApprovalService'; import type { ToolCallStarted, ToolProgress, ToolResultEvent, } from '@moonshot-ai/agent-core-v2/agent/toolExecutor/toolExecutorEvents'; import type { AgentStatusUpdated } from '@moonshot-ai/agent-core-v2/agent/usage/usageEvents'; import type { PlanRevision } from '@moonshot-ai/agent-core-v2/features/plan/planOps'; import type { SubagentSuspended } from '@moonshot-ai/agent-core-v2/features/swarm/session/sessionSwarmService'; import type { SubagentCancelled, SubagentCompleted, SubagentFailed, SubagentSpawned, SubagentStarted, } from '@moonshot-ai/agent-core-v2/session/subagent/mirrorAgentRun'; import { projectTranscriptUserOrigin, projectTranscriptUserTurnOrigin, type AgentRef, type AgentUsageMeta, type StepHeader, type StepUsage, type TextFrame, type ToolCallFrame, type ToolFrameProgress, type TranscriptAttachment, type TranscriptFrame, type TranscriptInteraction, type TranscriptMarker, type TranscriptOperation, type TranscriptPrompt, type TranscriptTask, type TranscriptTodo, type TranscriptUsage, type TranscriptUserOrigin, type TurnHeader, type TurnOrigin, type TurnState, } from '@moonshot-ai/transcript'; import { toLegacyPhase, type LegacyActivityApproval } from '../legacyStatus/legacyStatus'; import { LegacyActivityTracker, phaseFromDomainEvent } from '../legacyStatus/legacyActivity'; import { toWireQuestion } from '../../protocol/question-wire'; import { projectPromptContentParts } from '../messages/messageProjection'; export interface ProjectorInteraction { readonly id: string; readonly kind: 'approval' | 'question'; readonly payload: unknown; readonly createdAt: number; } type PlanRevisionEvent = { readonly type: 'plan.revision' } & PlanRevision; type PromptQueuedEvent = { readonly type: 'prompt.queued' } & PromptQueued; type PromptSubmittedEvent = { readonly type: 'prompt.submitted' } & PromptSubmitted; type PromptStartedEvent = { readonly type: 'prompt.started' } & PromptStarted; type PromptCompletedEvent = { readonly type: 'prompt.completed' } & PromptCompleted; type PromptAbortedEvent = { readonly type: 'prompt.aborted' } & PromptAborted; type PromptSteeredEvent = { readonly type: 'prompt.steered' } & PromptSteered; type TurnSteerEvent = { readonly type: 'turn.steer' } & TurnSteer; type SteerFileAttachment = { readonly name: string; readonly mediaType: string; readonly size: number }; export type ProjectorBusEvent = | PlanRevisionEvent | ({ readonly type: 'turn.started' } & TurnStarted) | ({ readonly type: 'turn.ended' } & TurnEnded) | ({ readonly type: 'turn.step.started' } & TurnStepStarted) | ({ readonly type: 'turn.step.completed' } & TurnStepCompleted) | ({ readonly type: 'turn.step.interrupted' } & TurnStepInterrupted) | ({ readonly type: 'turn.step.retrying' } & TurnStepRetrying) | ({ readonly type: 'assistant.delta' } & AssistantDelta) | ({ readonly type: 'thinking.delta' } & ThinkingDelta) | ({ readonly type: 'tool.call.delta' } & ToolCallDelta) | ({ readonly type: 'tool.progress' } & ToolProgress) | ({ readonly type: 'tool.call.started' } & ToolCallStarted) | ({ readonly type: 'tool.result' } & ToolResultEvent) | ({ readonly type: 'permission.approval.requested' } & PermissionApprovalRequested) | ({ readonly type: 'permission.approval.resolved' } & PermissionApprovalResolved) | ({ readonly type: 'task.started' } & TaskStarted) | ({ readonly type: 'task.terminated' } & TaskTerminatedNotice) | ({ readonly type: 'task.notified' } & TaskNotified) | ({ readonly type: 'shell.started' } & ShellStarted) | ({ readonly type: 'shell.output' } & ShellOutput) | ({ readonly type: 'shell.completed' } & ShellCompleted) | ({ readonly type: 'subagent.spawned' } & SubagentSpawned) | ({ readonly type: 'subagent.started' } & SubagentStarted) | ({ readonly type: 'subagent.completed' } & SubagentCompleted) | ({ readonly type: 'subagent.failed' } & SubagentFailed) | ({ readonly type: 'subagent.cancelled' } & SubagentCancelled) | ({ readonly type: 'subagent.suspended' } & SubagentSuspended) | ({ readonly type: 'goal.updated' } & GoalUpdated) | ({ readonly type: 'agent.status.updated' } & AgentStatusUpdated) | PromptQueuedEvent | PromptSubmittedEvent | PromptStartedEvent | PromptCompletedEvent | PromptAbortedEvent | PromptSteeredEvent | TurnSteerEvent | ({ readonly type: 'hook.result' } & HookResult) | ({ readonly type: 'skill.activated' } & SkillActivated) | ({ readonly type: 'plugin_command.activated' } & PluginCommandActivated) | ({ readonly type: 'cron.fired' } & CronFired) | ({ readonly type: 'compaction.started' } & CompactionStarted) | ({ readonly type: 'compaction.blocked' } & CompactionBlocked) | ({ readonly type: 'compaction.cancelled' } & CompactionCancelled) | ({ readonly type: 'compaction.completed' } & CompactionCompleted) | ({ readonly type: 'context.spliced' } & ContextSpliced) | ({ readonly type: 'context.undone' } & ContextUndone) | ({ readonly type: 'error' } & AgentErrorEvent) | ({ readonly type: 'warning' } & WarningIssued); export type ProjectorFrameLookup = ( turnId: string, stepId: string, ) => readonly TranscriptFrame[] | undefined; export type ProjectorToolFrameLookup = (toolCallId: string) => ToolFrameRecord | undefined; export type ProjectorStepOrdinalLookup = (turnId: string) => number | undefined; export type ProjectorTurnLookup = (turnId: string) => TurnHeader | undefined; export type ProjectorPlanRevisionKey = (key: string) => string; export interface ProjectorLookups { readonly stepFrames?: ProjectorFrameLookup; readonly toolFrame?: ProjectorToolFrameLookup; readonly stepOrdinal?: ProjectorStepOrdinalLookup; readonly turn?: ProjectorTurnLookup; readonly resolvePlanRevisionKey?: ProjectorPlanRevisionKey; readonly activitySnapshot?: () => AgentActivitySnapshot; readonly pendingApprovals?: () => readonly LegacyActivityApproval[]; } interface OpenTextFrame { readonly frameId: string; offset: number; text: string; } export interface ToolFrameRecord { readonly turnId: string; readonly stepId: string; readonly frame: ToolCallFrame; } export class AgentTranscriptProjector { private currentTurn: TurnHeader | undefined; private currentStep: StepHeader | undefined; private pendingTaskNotifications: { text: string; taskId: string | undefined }[] = []; private pendingSteers: { input: readonly ContentPart[]; files: readonly SteerFileAttachment[]; promptIds: readonly string[] | undefined; origin: TranscriptUserOrigin; }[] = []; private unpairedSteerPromptIds: string[][] = []; private readonly stepOrdinals = new Map(); private frameOrdinal = 0; private attachmentOrdinal = 0; private openText: OpenTextFrame | undefined; private openThinking: OpenTextFrame | undefined; private readonly toolFrames = new Map(); private readonly tasks = new Map(); private readonly shellTasks = new Map(); private readonly subagentTaskIds = new Map(); private activityTracker: LegacyActivityTracker | undefined; seedSubagentTask(info: { readonly taskId: string; readonly agentId: string; readonly description: string; readonly status: string; readonly detached: boolean; readonly startedAt: number; }): TranscriptOperation[] { if (info.status !== 'running') return []; this.subagentTaskIds.set(info.agentId, info.taskId); const task = this.upsertTask(info.taskId, (prev) => ({ taskId: info.taskId, kind: 'subagent', state: 'running', detached: info.detached, description: info.description, agentId: info.agentId, outputTail: prev?.outputTail ?? '', startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt), endedAt: prev?.endedAt, })); return [{ op: 'task.upsert', task }]; } seedActiveTurn(info: { turnId: number; promptId?: string }): void { const turnId = `t${info.turnId}`; const prev = this.lookups?.turn?.(turnId); this.currentTurn = { kind: 'turn', turnId, ordinal: info.turnId, state: 'running', triggerPromptId: info.promptId ?? prev?.triggerPromptId, origin: prev?.origin ?? { kind: 'other' }, prompt: prev?.prompt, attachmentIds: prev?.attachmentIds, startedAt: prev?.startedAt, }; } private readonly interactions = new Map(); private readonly prompts = new Map(); private readonly stepUsageByTurn = new Map(); private markerSeq = 0; private planModeActive = false; constructor( readonly agentId: string, private readonly sessionId: string, private readonly lookups?: ProjectorLookups, ) {} map(event: ProjectorBusEvent): TranscriptOperation[] { const ops = this.mapEvent(event); const phase = this.phaseFor(event); if (phase === undefined) return ops; return [...ops, { op: 'meta.merge', meta: { agent: { phase } } }]; } private phaseFor(event: ProjectorBusEvent): ReturnType { if (this.lookups?.activitySnapshot === undefined || this.lookups.pendingApprovals === undefined) { return undefined; } this.activityTracker ??= new LegacyActivityTracker( this.lookups.activitySnapshot, this.lookups.pendingApprovals, ); return phaseFromDomainEvent(this.activityTracker, event); } private mapEvent(event: ProjectorBusEvent): TranscriptOperation[] { switch (event.type) { case 'plan.revision': return this.onPlanRevision(event); case 'turn.started': return this.onTurnStarted(event); case 'turn.ended': return this.onTurnEnded(event); case 'turn.step.started': return this.onStepStarted(event); case 'turn.step.completed': return this.onStepCompleted(event); case 'turn.step.interrupted': return this.onStepFinished(event); case 'turn.step.retrying': return this.onStepRetrying(event); case 'assistant.delta': return this.onTextDelta(event.turnId, 'assistant', event.delta); case 'thinking.delta': return this.onTextDelta(event.turnId, 'thinking', event.delta); case 'tool.call.delta': return this.onToolCallDelta(event); case 'tool.progress': return this.onToolProgress(event); case 'tool.call.started': return this.onToolCallStarted(event); case 'tool.result': return this.onToolResult(event); case 'permission.approval.requested': case 'permission.approval.resolved': return []; case 'task.started': case 'task.terminated': return this.onTaskLifecycle(event); case 'task.notified': return this.onTaskNotified(event); case 'shell.started': return this.onShellStarted(event); case 'shell.output': return this.onShellOutput(event); case 'shell.completed': return this.onShellCompleted(event); case 'subagent.spawned': return this.onSubagentSpawned(event); case 'subagent.started': case 'subagent.completed': case 'subagent.failed': case 'subagent.cancelled': case 'subagent.suspended': return this.onSubagentRun(event); case 'goal.updated': return this.onGoalUpdated(event); case 'agent.status.updated': return this.onAgentStatusUpdated(event); case 'prompt.queued': return this.onPromptQueued(event); case 'prompt.submitted': return this.onPromptSubmitted(event); case 'prompt.started': return this.onPromptStarted(event); case 'prompt.completed': return this.onPromptCompleted(event); case 'prompt.aborted': return this.onPromptAborted(event); case 'prompt.steered': return this.onPromptSteered(event); case 'turn.steer': return this.onTurnSteered(event); case 'hook.result': return [this.markerOp('hook', restOf(event))]; case 'skill.activated': return [this.markerOp('skill', restOf(event))]; case 'plugin_command.activated': return [this.markerOp('skill', { ...restOf(event), variant: 'plugin_command' })]; case 'cron.fired': return [this.markerOp('cron.fired', restOf(event))]; case 'compaction.started': case 'compaction.blocked': case 'compaction.cancelled': case 'compaction.completed': return [ this.markerOp('compaction', { phase: event.type.slice('compaction.'.length), ...restOf(event), }), ]; case 'context.spliced': return [this.markerOp('undo', restOf(event))]; case 'context.undone': return []; case 'error': return [this.noticeOp('error', event.message, restOf(event))]; case 'warning': return [this.noticeOp('warning', event.message, restOf(event))]; default: return []; } } private onTurnStarted(event: { turnId: number; promptId?: string; origin: unknown; prompt?: string; promptAttachments?: readonly ( | { kind: 'image' | 'video' | 'audio'; fileId: string; name?: string } | { kind: 'file'; name: string; mediaType: string; size: number; path: string } )[]; }): TranscriptOperation[] { const n = event.turnId; const turnId = `t${n}`; const ops: TranscriptOperation[] = []; const attachmentIds: string[] = []; for (const input of event.promptAttachments ?? []) { const attachment: TranscriptAttachment = input.kind === 'file' ? { attachmentId: `${turnId}.att${attachmentIds.length + 1}`, mediaType: input.mediaType, name: input.name, size: input.size, } : { attachmentId: `${turnId}.att${attachmentIds.length + 1}`, mediaType: `${input.kind}/*`, name: input.name, source: { kind: 'session_media', fileId: input.fileId }, }; ops.push({ op: 'attachment.upsert', attachment }); attachmentIds.push(attachment.attachmentId); } this.currentTurn = { kind: 'turn', turnId, triggerPromptId: event.promptId, ordinal: n, state: 'running', origin: mapTurnOrigin(event.origin), prompt: event.prompt, attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined, startedAt: nowIso(), }; this.currentStep = undefined; this.pendingTaskNotifications = []; this.pendingSteers = []; this.openText = undefined; this.openThinking = undefined; ops.push({ op: 'turn.upsert', turn: this.currentTurn }); ops.push({ op: 'meta.merge', meta: { activity: 'turn' } }); return ops; } private onTurnEnded(event: { time?: number; turnId: number; reason: 'completed' | 'cancelled' | 'failed' | 'blocked'; error?: { message: string }; durationMs?: number; interruptReason?: string; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; this.flushOpenFrames(ops); const turnId = `t${event.turnId}`; if (this.currentStep !== undefined && this.currentStep.state === 'running') { const step: StepHeader = { ...this.currentStep, state: 'interrupted', endedAt: nowIso() }; this.currentStep = step; ops.push({ op: 'step.upsert', turnId: step.turnId, step }); } if (this.currentStep === undefined && this.pendingSteers.length > 0) { const ordinal = (this.stepOrdinals.get(turnId) ?? this.lookups?.stepOrdinal?.(turnId) ?? 0) + 1; const step: StepHeader = { kind: 'step', stepId: `${turnId}.${ordinal}`, turnId, ordinal, state: 'interrupted', endedAt: nowIso(), }; this.stepOrdinals.set(turnId, ordinal); this.currentStep = step; ops.push({ op: 'step.upsert', turnId, step }); } if (this.currentStep !== undefined) { for (const pending of this.pendingSteers) { this.steerUserFrame( ops, turnId, this.currentStep.stepId, pending.input, pending.files, pending.promptIds, pending.origin, ); } } this.pendingSteers = []; const prev = this.currentTurn?.turnId === turnId ? this.currentTurn : this.lookups?.turn?.(turnId); const state = mapTurnEndState(event.reason); this.currentTurn = { kind: 'turn', turnId, ordinal: event.turnId, state, triggerPromptId: prev?.triggerPromptId, origin: prev?.origin ?? { kind: 'other' }, prompt: prev?.prompt, attachmentIds: prev?.attachmentIds, startedAt: prev?.startedAt, endedAt: event.time === undefined ? nowIso() : epochMsToIso(event.time), durationMs: event.durationMs, error: event.error?.message, usage: this.takeTurnUsage(turnId), }; ops.push({ op: 'turn.upsert', turn: this.currentTurn }); ops.push({ op: 'meta.merge', meta: { activity: 'idle' } }); this.currentStep = undefined; this.pendingTaskNotifications = []; if (event.reason === 'cancelled' && event.interruptReason === 'user_cancelled') { ops.push( this.markerOp('interruption', { turnId: event.turnId, reason: event.interruptReason }), ); } return ops; } private takeTurnUsage(turnId: string): TranscriptUsage | undefined { const usages = this.stepUsageByTurn.get(turnId); this.stepUsageByTurn.delete(turnId); if (usages === undefined || usages.length === 0) return undefined; let inputOther = 0; let output = 0; let inputCacheRead = 0; let inputCacheCreation = 0; for (const usage of usages) { inputOther += usage.inputOther; output += usage.output; inputCacheRead += usage.inputCacheRead; inputCacheCreation += usage.inputCacheCreation; } return { inputTokens: inputOther + inputCacheCreation, cachedTokens: inputCacheRead, outputTokens: output, }; } private onStepStarted(event: { turnId: number; step: number }): TranscriptOperation[] { const turnId = `t${event.turnId}`; const stepId = `${turnId}.${event.step}`; this.stepOrdinals.set(turnId, event.step); this.currentStep = { kind: 'step', stepId, turnId, ordinal: event.step, state: 'running', startedAt: nowIso(), }; this.frameOrdinal = 0; this.attachmentOrdinal = 0; this.openText = undefined; this.openThinking = undefined; const ops: TranscriptOperation[] = [{ op: 'step.upsert', turnId, step: this.currentStep }]; for (const pending of this.pendingTaskNotifications) { ops.push({ op: 'frame.upsert', turnId, stepId, frame: { kind: 'text', frameId: `${stepId}.f${++this.frameOrdinal}`, role: 'user', text: pending.text, taskId: pending.taskId, }, }); } this.pendingTaskNotifications = []; for (const pending of this.pendingSteers) { this.steerUserFrame(ops, turnId, stepId, pending.input, pending.files, pending.promptIds, pending.origin); } this.pendingSteers = []; return ops; } private onStepCompleted(event: { turnId: number; step: number; usage?: StepUsage; finishReason?: string; rawFinishReason?: string; providerFinishReason?: string; llmFirstTokenLatencyMs?: number; llmStreamDurationMs?: number; llmRequestBuildMs?: number; llmServerFirstTokenMs?: number; llmServerDecodeMs?: number; llmClientConsumeMs?: number; llmClientBlockedMs?: number; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; this.flushOpenFrames(ops); const turnId = `t${event.turnId}`; const stepId = `${turnId}.${event.step}`; const prev = this.currentStep?.stepId === stepId ? this.currentStep : undefined; if (event.usage !== undefined) { const usages = this.stepUsageByTurn.get(turnId) ?? []; usages.push(event.usage); this.stepUsageByTurn.set(turnId, usages); } this.currentStep = { kind: 'step', stepId, turnId, ordinal: event.step, state: 'completed', startedAt: prev?.startedAt, endedAt: nowIso(), usage: event.usage, finishReason: event.finishReason ?? event.rawFinishReason ?? event.providerFinishReason, timing: { llmFirstTokenLatencyMs: event.llmFirstTokenLatencyMs, llmStreamDurationMs: event.llmStreamDurationMs, llmRequestBuildMs: event.llmRequestBuildMs, llmServerFirstTokenMs: event.llmServerFirstTokenMs, llmServerDecodeMs: event.llmServerDecodeMs, llmClientConsumeMs: event.llmClientConsumeMs, llmClientBlockedMs: event.llmClientBlockedMs, }, }; ops.push({ op: 'step.upsert', turnId, step: this.currentStep }); return ops; } private onStepFinished(event: { type: 'turn.step.interrupted'; turnId: number; step: number; reason: string; message?: string; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; this.flushOpenFrames(ops); const turnId = `t${event.turnId}`; const stepId = `${turnId}.${event.step}`; const prev = this.currentStep?.stepId === stepId ? this.currentStep : undefined; this.currentStep = { kind: 'step', stepId, turnId, ordinal: event.step, state: 'interrupted', startedAt: prev?.startedAt, endedAt: nowIso(), endReason: event.reason, endMessage: event.message, }; ops.push({ op: 'step.upsert', turnId, step: this.currentStep }); return ops; } private onStepRetrying(event: { turnId: number; step: number; failedAttempt: number; nextAttempt: number; maxAttempts: number; delayMs: number; errorName: string; errorMessage: string; statusCode?: number; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const turnId = `t${event.turnId}`; const stepId = `${turnId}.${event.step}`; const prev = this.currentStep?.stepId === stepId ? this.currentStep : undefined; this.currentStep = { kind: 'step', stepId, turnId, ordinal: event.step, state: 'running', startedAt: prev?.startedAt, retry: { failedAttempt: event.failedAttempt, nextAttempt: event.nextAttempt, maxAttempts: event.maxAttempts, delayMs: event.delayMs, errorName: event.errorName, errorMessage: event.errorMessage, statusCode: event.statusCode, }, }; ops.push({ op: 'step.upsert', turnId, step: this.currentStep }); return ops; } private onTextDelta( turnNumber: number, kind: 'assistant' | 'thinking', delta: string, ): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const turnId = `t${turnNumber}`; const step = this.ensureStep(turnId, ops); let open = kind === 'assistant' ? this.openText : this.openThinking; open ??= this.adoptStreamFrame(turnId, step.stepId, kind); if (open === undefined) { const frameId = `${step.stepId}.f${++this.frameOrdinal}`; open = { frameId, offset: 0, text: '' }; ops.push({ op: 'frame.upsert', turnId, stepId: step.stepId, frame: kind === 'assistant' ? { kind: 'text', frameId, role: 'assistant', text: '' } : { kind: 'thinking', frameId, text: '' }, }); } ops.push({ op: 'append', target: { type: 'frame', turnId, stepId: step.stepId, frameId: open.frameId }, offset: open.offset, text: delta, }); open.offset += delta.length; open.text += delta; if (kind === 'assistant') this.openText = open; else this.openThinking = open; return ops; } private adoptStreamFrame( turnId: string, stepId: string, kind: 'assistant' | 'thinking', ): OpenTextFrame | undefined { const frames = this.lookups?.stepFrames?.(turnId, stepId); if (frames === undefined || frames.length === 0) return undefined; for (const frame of frames) { const match = /\.f(\d+)$/.exec(frame.frameId); if (match !== null) { this.frameOrdinal = Math.max(this.frameOrdinal, Number(match[1])); } } for (let i = frames.length - 1; i >= 0; i -= 1) { const frame = frames[i]; if (frame === undefined) continue; if (kind === 'assistant' && frame.kind === 'text' && frame.role === 'assistant') { return { frameId: frame.frameId, offset: frame.text.length, text: frame.text }; } if (kind === 'thinking' && frame.kind === 'thinking') { return { frameId: frame.frameId, offset: frame.text.length, text: frame.text }; } } return undefined; } private flushOpenFrames(ops: TranscriptOperation[]): void { const step = this.currentStep; for (const open of [this.openText, this.openThinking]) { if (open === undefined || step === undefined) continue; const isText = open === this.openText; ops.push({ op: 'frame.upsert', turnId: step.turnId, stepId: step.stepId, frame: isText ? { kind: 'text', frameId: open.frameId, role: 'assistant', text: open.text } : { kind: 'thinking', frameId: open.frameId, text: open.text }, }); } this.openText = undefined; this.openThinking = undefined; } private ensureStep(turnId: string, ops: TranscriptOperation[]): StepHeader { if (this.currentStep !== undefined && this.currentStep.turnId === turnId) { return this.currentStep; } const ordinal = this.lookups?.stepOrdinal?.(turnId) ?? this.stepOrdinals.get(turnId) ?? 1; this.currentStep = { kind: 'step', stepId: `${turnId}.${ordinal}`, turnId, ordinal, state: 'running', startedAt: nowIso(), }; ops.push({ op: 'step.upsert', turnId, step: this.currentStep }); return this.currentStep; } private onToolCallDelta(event: { turnId: number; toolCallId: string; name?: string; argumentsPart?: string; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const prev = this.toolFrames.get(event.toolCallId); if (prev !== undefined) { const frame: ToolCallFrame = { ...prev.frame, inputText: (prev.frame.inputText ?? '') + (event.argumentsPart ?? ''), }; this.toolFrames.set(event.toolCallId, { ...prev, frame }); ops.push({ op: 'frame.upsert', turnId: prev.turnId, stepId: prev.stepId, frame }); return ops; } const turnId = `t${event.turnId}`; const step = this.ensureStep(turnId, ops); const frameId = `${step.stepId}.${event.toolCallId}`; const frame: ToolCallFrame = { kind: 'tool', frameId, toolCallId: event.toolCallId, name: event.name ?? '', state: 'running', inputText: event.argumentsPart ?? '', }; this.toolFrames.set(event.toolCallId, { turnId, stepId: step.stepId, frame }); ops.push({ op: 'frame.upsert', turnId, stepId: step.stepId, frame }); return ops; } private onToolProgress(event: { toolCallId: string; update: ToolFrameProgress; }): TranscriptOperation[] { const hit = this.toolFrames.get(event.toolCallId) ?? this.adoptToolFrame(event.toolCallId); if (hit === undefined) return []; const frame: ToolCallFrame = { ...hit.frame, progress: { kind: event.update.kind, text: event.update.text, percent: event.update.percent, customKind: event.update.customKind, customData: event.update.customData, }, }; this.toolFrames.set(event.toolCallId, { ...hit, frame }); return [{ op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame }]; } private onToolCallStarted(event: { turnId: number; toolCallId: string; name: string; args: unknown; display?: unknown; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const turnId = `t${event.turnId}`; const step = this.ensureStep(turnId, ops); const frameId = `${step.stepId}.${event.toolCallId}`; const input = parseToolArgs(event.args); const frame: ToolCallFrame = { kind: 'tool', frameId, toolCallId: event.toolCallId, name: event.name, state: 'running', input, inputText: this.toolFrames.get(event.toolCallId)?.frame.inputText, display: event.display, todoId: event.name === TODO_LIST_TOOL_NAME && todoWriteItems(input) !== undefined ? TODO_ENTITY_ID : undefined, }; this.toolFrames.set(event.toolCallId, { turnId, stepId: step.stepId, frame }); ops.push({ op: 'frame.upsert', turnId, stepId: step.stepId, frame }); return ops; } private onToolResult(event: { toolCallId: string; output: unknown; isError?: boolean; }): TranscriptOperation[] { const hit = this.toolFrames.get(event.toolCallId) ?? this.adoptToolFrame(event.toolCallId); if (hit === undefined) return []; const isError = event.isError === true; const frame: ToolCallFrame = { ...hit.frame, state: isError ? 'error' : 'done', output: event.output, error: isError && typeof event.output === 'string' ? event.output : undefined, }; this.toolFrames.set(event.toolCallId, { ...hit, frame }); const ops: TranscriptOperation[] = [ { op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame }, ]; if (!isError && frame.name === TODO_LIST_TOOL_NAME) { const items = todoWriteItems(frame.input); if (items !== undefined) { const todo: TranscriptTodo = { todoId: TODO_ENTITY_ID, items, updatedAt: nowIso() }; ops.push({ op: 'todo.upsert', todo }); } } return ops; } private adoptToolFrame(toolCallId: string): ToolFrameRecord | undefined { const hit = this.lookups?.toolFrame?.(toolCallId); if (hit === undefined) return undefined; this.toolFrames.set(toolCallId, hit); return hit; } private onTaskNotified(event: { notificationType: string; title: string; body: string; severity: string; sourceKind: string; sourceId: string; }): TranscriptOperation[] { const step = this.currentStep; const turn = this.currentTurn; if (turn === undefined || turn.state !== 'running') return []; const text = `${event.title}\n${event.body}`.trim(); if (step !== undefined && step.state === 'running') { const frame: TextFrame = { kind: 'text', frameId: `${step.stepId}.f${++this.frameOrdinal}`, role: 'user', text, taskId: event.sourceId, }; return [{ op: 'frame.upsert', turnId: turn.turnId, stepId: step.stepId, frame }]; } if (turn.origin?.kind === 'task' && (turn.origin.taskId === undefined || turn.origin.taskId === event.sourceId)) return []; this.pendingTaskNotifications.push({ text, taskId: event.sourceId }); return []; } private onTaskLifecycle(event: { type: 'task.started' | 'task.terminated'; info: { taskId: string; kind: string; description: string; status: TranscriptTask['state']; detached?: boolean; agentId?: string; startedAt: number; endedAt: number | null; }; }): TranscriptOperation[] { const { info } = event; const task = this.upsertTask(info.taskId, (prev) => ({ taskId: info.taskId, kind: mapTaskKind(info.kind), state: info.status, detached: info.detached ?? prev?.detached ?? true, description: info.description, agentId: info.agentId ?? prev?.agentId, outputTail: prev?.outputTail ?? '', startedAt: prev?.startedAt ?? epochMsToIso(info.startedAt), endedAt: info.endedAt === null ? prev?.endedAt : epochMsToIso(info.endedAt), resultSummary: prev?.resultSummary, usage: prev?.usage, error: prev?.error, stateReason: prev?.stateReason, model: prev?.model, thinkingEffort: prev?.thinkingEffort, })); const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; if (event.type === 'task.started') { if (info.kind === 'agent' && typeof info.agentId === 'string' && info.agentId.length > 0) { this.subagentTaskIds.set(info.agentId, info.taskId); } ops.push({ op: 'taskref.upsert', item: { kind: 'taskref', refId: `ref-${info.taskId}`, taskId: info.taskId, at: nowIso() }, }); } return ops; } private onShellStarted(event: { commandId: string; taskId: string }): TranscriptOperation[] { this.shellTasks.set(event.commandId, event.taskId); const task = this.upsertTask(event.taskId, (prev) => ({ taskId: event.taskId, kind: 'shell', state: 'running', detached: prev?.detached ?? false, description: prev?.description, agentId: prev?.agentId, outputTail: prev?.outputTail ?? '', startedAt: prev?.startedAt ?? nowIso(), endedAt: prev?.endedAt, })); return [ { op: 'task.upsert', task }, { op: 'taskref.upsert', item: { kind: 'taskref', refId: `ref-${event.taskId}`, taskId: event.taskId, at: nowIso() }, }, ]; } private shellTaskId(event: { commandId: string; taskId?: string }): string { const taskId = this.shellTasks.get(event.commandId) ?? event.taskId ?? `shell-${event.commandId}`; this.shellTasks.set(event.commandId, taskId); return taskId; } private onShellOutput(event: { commandId: string; taskId?: string; update: { kind: string; text?: string }; }): TranscriptOperation[] { const taskId = this.shellTaskId(event); const text = event.update.text; if (typeof text !== 'string' || text.length === 0) return []; const ops: TranscriptOperation[] = []; let task = this.tasks.get(taskId); if (task === undefined) { task = this.upsertTask(taskId, (prev) => ({ taskId, kind: 'shell', state: 'running', detached: prev?.detached ?? false, description: prev?.description, agentId: prev?.agentId, outputTail: prev?.outputTail ?? '', startedAt: prev?.startedAt ?? nowIso(), endedAt: prev?.endedAt, })); ops.push( { op: 'task.upsert', task }, { op: 'taskref.upsert', item: { kind: 'taskref', refId: `ref-${taskId}`, taskId, at: nowIso() }, }, ); } const offset = task.outputTail.length; this.tasks.set(taskId, { ...task, outputTail: task.outputTail + text }); ops.push({ op: 'append', target: { type: 'task', taskId }, offset, text }); return ops; } private onShellCompleted(event: { commandId: string; taskId?: string; isError: boolean; }): TranscriptOperation[] { const taskId = this.shellTaskId(event); const hadTask = this.tasks.has(taskId); const task = this.upsertTask(taskId, (prev) => ({ taskId, kind: prev?.kind ?? 'shell', state: event.isError ? 'failed' : 'completed', detached: prev?.detached ?? false, description: prev?.description, agentId: prev?.agentId, outputTail: prev?.outputTail ?? '', startedAt: prev?.startedAt ?? nowIso(), endedAt: nowIso(), })); const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; if (!hadTask) { ops.push({ op: 'taskref.upsert', item: { kind: 'taskref', refId: `ref-${taskId}`, taskId, at: nowIso() }, }); } return ops; } private upsertTask( taskId: string, build: (prev: TranscriptTask | undefined) => TranscriptTask, ): TranscriptTask { const task = build(this.tasks.get(taskId)); this.tasks.set(taskId, task); return task; } private onSubagentSpawned(event: { subagentId: string; subagentName: string; parentToolCallId: string; description?: string; swarmIndex?: number; runInBackground: boolean; taskId?: string; model?: string; thinkingEffort?: string; }): TranscriptOperation[] { const taskKey = event.taskId ?? event.subagentId; if (event.taskId !== undefined) { this.subagentTaskIds.set(event.subagentId, event.taskId); } else { this.subagentTaskIds.delete(event.subagentId); } const task = this.upsertTask(taskKey, (prev) => { const newGeneration = prev !== undefined && prev.state !== 'running'; return { taskId: taskKey, kind: 'subagent', state: 'running', detached: event.runInBackground, description: event.description ?? prev?.description, agentId: event.subagentId, outputTail: prev?.outputTail ?? '', startedAt: newGeneration ? nowIso() : (prev?.startedAt ?? nowIso()), endedAt: newGeneration ? undefined : prev?.endedAt, model: event.model ?? prev?.model, thinkingEffort: event.thinkingEffort ?? prev?.thinkingEffort, }; }); const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; const hit = this.toolFrames.get(event.parentToolCallId) ?? this.adoptToolFrame(event.parentToolCallId); if (hit !== undefined) { const ref: AgentRef = { agentId: event.subagentId, role: event.swarmIndex !== undefined ? 'member' : 'child', }; const frame: ToolCallFrame = { ...hit.frame, agentRefs: [...(hit.frame.agentRefs ?? []), ref], }; this.toolFrames.set(event.parentToolCallId, { ...hit, frame }); ops.push({ op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame }); } return ops; } private onSubagentRun(event: { type: 'subagent.started' | 'subagent.completed' | 'subagent.failed' | 'subagent.cancelled' | 'subagent.suspended'; subagentId: string; resultSummary?: string; usage?: StepUsage; error?: string; reason?: string; }): TranscriptOperation[] { const terminal = event.type === 'subagent.completed' || event.type === 'subagent.failed' || event.type === 'subagent.cancelled'; const state: TranscriptTask['state'] = event.type === 'subagent.completed' ? 'completed' : event.type === 'subagent.failed' ? 'failed' : event.type === 'subagent.cancelled' ? 'killed' : 'running'; const build = (taskId: string) => (prev: TranscriptTask | undefined): TranscriptTask => { const newGeneration = !terminal && prev !== undefined && prev.state !== 'running'; return { taskId, kind: 'subagent', state, detached: prev?.detached ?? true, description: prev?.description, agentId: event.subagentId, outputTail: prev?.outputTail ?? '', startedAt: newGeneration ? nowIso() : (prev?.startedAt ?? nowIso()), endedAt: terminal ? nowIso() : newGeneration ? undefined : prev?.endedAt, resultSummary: newGeneration ? undefined : (event.resultSummary ?? prev?.resultSummary), usage: newGeneration ? undefined : (event.usage ?? prev?.usage), error: newGeneration ? undefined : (event.error ?? prev?.error), stateReason: event.reason ?? (newGeneration ? undefined : prev?.stateReason), model: prev?.model, thinkingEffort: prev?.thinkingEffort, }; }; const taskKey = this.subagentTaskIds.get(event.subagentId) ?? event.subagentId; const task = this.upsertTask(taskKey, build(taskKey)); const ops: TranscriptOperation[] = [{ op: 'task.upsert', task }]; if (taskKey !== event.subagentId && this.tasks.has(event.subagentId)) { const agentTask = this.upsertTask(event.subagentId, build(event.subagentId)); ops.push({ op: 'task.upsert', task: agentTask }); } return ops; } private onGoalUpdated(event: { readonly type: string; snapshot: { objective: string; status: 'active' | 'paused' | 'blocked' | 'complete'; completionCriterion?: string; tokensUsed: number; budget: { tokenBudget: number | null }; } | null; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const snapshot = event.snapshot; if (snapshot === null) { ops.push({ op: 'meta.merge', meta: { goal: null } }); } else { ops.push({ op: 'meta.merge', meta: { goal: { objective: snapshot.objective, status: snapshot.status, completionCriterion: snapshot.completionCriterion, budgetUsed: snapshot.tokensUsed, budgetLimit: snapshot.budget.tokenBudget ?? undefined, }, }, }); } ops.push(this.markerOp('goal', restOf(event))); return ops; } private onAgentStatusUpdated(event: { planMode?: boolean; swarmMode?: boolean; towerMode?: boolean; model?: string; thinkingEffort?: string; usage?: AgentUsageMeta; contextTokens?: number; maxContextTokens?: number; contextUsage?: number; permission?: 'manual' | 'yolo' | 'auto'; }): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const modes: { plan?: Record | null; swarm?: Record | null; tower?: Record | null; } = {}; if (event.planMode === true) { modes.plan = {}; this.planModeActive = true; } else if (event.planMode === false) { modes.plan = null; this.planModeActive = false; } if (event.swarmMode === true) modes.swarm = {}; else if (event.swarmMode === false) modes.swarm = null; if (event.towerMode === true) modes.tower = {}; else if (event.towerMode === false) modes.tower = null; if (modes.plan !== undefined || modes.swarm !== undefined || modes.tower !== undefined) { ops.push({ op: 'meta.merge', meta: { modes } }); } const agent: { model?: string; thinkingEffort?: string; usage?: AgentUsageMeta; contextTokens?: number; maxContextTokens?: number; contextUsage?: number; permission?: 'manual' | 'yolo' | 'auto'; } = {}; let hasStatusSlice = false; if (event.model !== undefined) { agent.model = event.model; hasStatusSlice = true; } if (event.thinkingEffort !== undefined) { agent.thinkingEffort = event.thinkingEffort; hasStatusSlice = true; } if (event.usage !== undefined) { agent.usage = event.usage; hasStatusSlice = true; } if (event.contextTokens !== undefined) { agent.contextTokens = event.contextTokens; hasStatusSlice = true; } if (event.maxContextTokens !== undefined) { agent.maxContextTokens = event.maxContextTokens; hasStatusSlice = true; } if (event.contextUsage !== undefined) { agent.contextUsage = event.contextUsage; hasStatusSlice = true; } if (event.permission !== undefined) { agent.permission = event.permission; hasStatusSlice = true; } if (hasStatusSlice) { ops.push({ op: 'meta.merge', meta: { agent } }); } return ops; } private onPlanRevision(event: PlanRevisionEvent): TranscriptOperation[] { const path = this.lookups?.resolvePlanRevisionKey?.(event.key) ?? event.key; const { key: _key, ...rest } = restOf(event); const payload = { ...rest, path }; const ops: TranscriptOperation[] = [this.markerOp('plan.revision', payload)]; if (this.planModeActive) { ops.push({ op: 'meta.merge', meta: { modes: { plan: { reviewPath: path, version: event.version } } }, }); } return ops; } private markerOp(marker: string, payload: unknown): TranscriptOperation { this.markerSeq += 1; const item: TranscriptMarker = { kind: 'marker', markerId: `live-m${this.markerSeq}`, marker, payload, at: nowIso(), }; return { op: 'marker.upsert', item }; } private noticeOp( level: 'error' | 'warning' | 'info', message: string, eventPayload: unknown, ): TranscriptOperation { return this.markerOp('notice', { level, message, event: eventPayload }); } private onPromptQueued(event: PromptQueuedEvent): TranscriptOperation[] { const prompt = this.upsertPrompt(event.promptId, (prev) => ({ promptId: event.promptId, status: 'queued', userMessageId: prev?.userMessageId, content: projectPromptContentParts(event.content), clientMetadata: event.clientMetadata ?? prev?.clientMetadata, createdAt: prev?.createdAt ?? nowIso(), })); return [{ op: 'prompt.upsert', prompt }]; } private onPromptSubmitted(event: PromptSubmittedEvent): TranscriptOperation[] { const prompt = this.upsertPrompt(event.promptId, (prev) => ({ promptId: event.promptId, status: prev !== undefined && isTerminalPromptStatus(prev.status) ? prev.status : event.status, userMessageId: event.userMessageId, content: projectPromptContentParts(event.content), clientMetadata: event.clientMetadata ?? prev?.clientMetadata, createdAt: prev?.createdAt ?? event.createdAt, finishedAt: prev?.finishedAt, steeredAt: prev?.steeredAt, })); return [{ op: 'prompt.upsert', prompt }]; } private onPromptStarted(event: PromptStartedEvent): TranscriptOperation[] { const prompt = this.upsertPrompt(event.promptId, (prev) => ({ promptId: event.promptId, status: 'running', userMessageId: prev?.userMessageId, content: prev?.content, clientMetadata: prev?.clientMetadata, createdAt: prev?.createdAt ?? new Date().toISOString(), finishedAt: prev?.finishedAt, steeredAt: prev?.steeredAt, })); return [{ op: 'prompt.upsert', prompt }]; } private onPromptCompleted(event: PromptCompletedEvent): TranscriptOperation[] { const prompt = this.upsertPrompt(event.promptId, (prev) => ({ promptId: event.promptId, status: event.reason ?? 'completed', userMessageId: prev?.userMessageId, content: prev?.content, clientMetadata: prev?.clientMetadata, createdAt: prev?.createdAt ?? event.finishedAt, finishedAt: event.finishedAt, steeredAt: prev?.steeredAt, })); return [{ op: 'prompt.upsert', prompt }]; } private onPromptAborted(event: PromptAbortedEvent): TranscriptOperation[] { const prompt = this.upsertPrompt(event.promptId, (prev) => ({ promptId: event.promptId, status: 'aborted', userMessageId: prev?.userMessageId, content: prev?.content, clientMetadata: prev?.clientMetadata, createdAt: prev?.createdAt ?? event.abortedAt, finishedAt: event.abortedAt, steeredAt: prev?.steeredAt, })); return [{ op: 'prompt.upsert', prompt }]; } private onPromptSteered(event: PromptSteeredEvent): TranscriptOperation[] { const ops: TranscriptOperation[] = []; const active = this.upsertPrompt(event.activePromptId, (prev) => ({ promptId: event.activePromptId, status: prev?.status ?? 'running', userMessageId: prev?.userMessageId, content: projectPromptContentParts(event.content), clientMetadata: prev?.clientMetadata, createdAt: prev?.createdAt ?? event.steeredAt, finishedAt: prev?.finishedAt, steeredAt: event.steeredAt, })); ops.push({ op: 'prompt.upsert', prompt: active }); this.unpairedSteerPromptIds.push([...event.promptIds]); for (const promptId of event.promptIds) { const steered = this.upsertPrompt(promptId, (prev) => ({ promptId, status: 'completed', userMessageId: prev?.userMessageId, content: prev?.content, clientMetadata: prev?.clientMetadata, createdAt: prev?.createdAt ?? event.steeredAt, finishedAt: event.steeredAt, steeredAt: event.steeredAt, })); ops.push({ op: 'prompt.upsert', prompt: steered }); } return ops; } private onTurnSteered(event: TurnSteerEvent): TranscriptOperation[] { const origin = event.origin; if (origin.kind !== 'user' && origin.kind !== 'skill_activation') return []; const frameOrigin = projectTranscriptUserOrigin(origin); if (frameOrigin === undefined) return []; const turn = this.currentTurn; if (turn !== undefined && turn.state !== 'running') return []; const skip = origin.kind === 'user' ? origin.skillActivations?.length ?? 0 : 0; const input = skip > 0 ? event.input.slice(skip) : event.input; const files = origin.attachments ?? []; const step = this.currentStep; if (step !== undefined && step.state === 'running') { const ops: TranscriptOperation[] = []; this.steerUserFrame( ops, step.turnId, step.stepId, input, files, origin.kind === 'user' ? this.unpairedSteerPromptIds.shift() : undefined, frameOrigin, ); return ops; } this.pendingSteers.push({ input, files, promptIds: origin.kind === 'user' ? this.unpairedSteerPromptIds.shift() : undefined, origin: frameOrigin, }); return []; } private steerUserFrame( ops: TranscriptOperation[], turnId: string, stepId: string, input: readonly ContentPart[], files: readonly SteerFileAttachment[], promptIds: readonly string[] | undefined, origin: TranscriptUserOrigin, ): void { const texts: string[] = []; const attachmentIds: string[] = []; for (const part of input) { if (part.type === 'text') { texts.push(part.text); continue; } const ref = daemonFileRefFromPart(part); if (ref === undefined) continue; const attachment: TranscriptAttachment = { attachmentId: `${stepId}.att${++this.attachmentOrdinal}`, mediaType: `${ref.kind}/*`, name: part.type === 'image_url' ? part.imageUrl.name : part.type === 'video_url' ? part.videoUrl.name : undefined, source: { kind: 'session_media', fileId: ref.ref.fileId }, }; ops.push({ op: 'attachment.upsert', attachment }); attachmentIds.push(attachment.attachmentId); } for (const file of files) { const attachment: TranscriptAttachment = { attachmentId: `${stepId}.att${++this.attachmentOrdinal}`, mediaType: file.mediaType, name: file.name, size: file.size, }; ops.push({ op: 'attachment.upsert', attachment }); attachmentIds.push(attachment.attachmentId); } ops.push({ op: 'frame.upsert', turnId, stepId, frame: { kind: 'text', frameId: `${stepId}.f${++this.frameOrdinal}`, role: 'user', text: texts.join(''), attachmentIds: attachmentIds.length > 0 ? attachmentIds : undefined, promptIds, origin, }, }); } private upsertPrompt( promptId: string, build: (prev: TranscriptPrompt | undefined) => TranscriptPrompt, ): TranscriptPrompt { const prompt = build(this.prompts.get(promptId)); this.prompts.set(promptId, prompt); return prompt; } mapInteractionRequested(interaction: ProjectorInteraction): TranscriptOperation[] { const payload = interaction.payload as { toolCallId?: unknown }; const toolCallId = typeof payload.toolCallId === 'string' ? payload.toolCallId : undefined; const entity: TranscriptInteraction = { interactionId: interaction.id, interactionKind: interaction.kind, toolCallId, state: 'pending', request: this.wireInteractionRequest(interaction), }; this.interactions.set(interaction.id, entity); return [{ op: 'interaction.upsert', interaction: entity }]; } private wireInteractionRequest(interaction: ProjectorInteraction): unknown { if (interaction.kind !== 'question') return interaction.payload; try { return toWireQuestion(interaction, this.sessionId); } catch { return interaction.payload; } } mapInteractionResolved(id: string, response: unknown): TranscriptOperation[] { const record = this.interactions.get(id); if (record === undefined) return []; this.interactions.delete(id); const state = mapInteractionEndState(record.interactionKind, response); const ops: TranscriptOperation[] = [ { op: 'interaction.upsert', interaction: { ...record, state, response } }, ]; const toolCallId = record.toolCallId; if (toolCallId !== undefined) { const hit = this.toolFrames.get(toolCallId) ?? this.adoptToolFrame(toolCallId); if (hit !== undefined) { const toolFrame: ToolCallFrame = { ...hit.frame, approvalId: id }; this.toolFrames.set(toolCallId, { ...hit, frame: toolFrame }); ops.push({ op: 'frame.upsert', turnId: hit.turnId, stepId: hit.stepId, frame: toolFrame }); } } return ops; } } function nowIso(): string { return new Date().toISOString(); } function isTerminalPromptStatus(status: TranscriptPrompt['status']): boolean { return status === 'completed' || status === 'failed' || status === 'aborted' || status === 'blocked'; } function epochMsToIso(value: number): string { return new Date(value).toISOString(); } function restOf(event: { readonly type: string; readonly time?: number }): Record { const { type: _type, time: _time, ...rest } = event; return rest; } function mapTurnOrigin(origin: unknown): TurnOrigin { const candidate = origin as { kind?: unknown } | null | undefined; const kind = typeof candidate?.kind === 'string' ? candidate.kind : undefined; switch (kind) { case 'user': return projectTranscriptUserTurnOrigin(origin); case 'cron_job': case 'cron_missed': { const jobId = (candidate as { jobId?: unknown }).jobId; return { kind: 'cron', taskId: typeof jobId === 'string' ? jobId : undefined, payload: origin, }; } case 'task': case 'background_task': { const taskId = (candidate as { taskId?: unknown }).taskId; return typeof taskId === 'string' ? { kind: 'task', taskId, payload: origin } : { kind: 'other', payload: origin }; } case 'hook_result': return { kind: 'hook', payload: origin }; case 'compaction_summary': return { kind: 'compaction', payload: origin }; case 'shell_command': return { kind: 'user', payload: origin }; default: return { kind: 'other', payload: origin }; } } function mapTurnEndState(reason: 'completed' | 'cancelled' | 'failed' | 'blocked'): TurnState { switch (reason) { case 'completed': return 'completed'; case 'cancelled': return 'cancelled'; case 'failed': case 'blocked': return 'failed'; } } function mapTaskKind(kind: string): TranscriptTask['kind'] { switch (kind) { case 'process': return 'shell'; case 'agent': return 'subagent'; default: return 'other'; } } function mapInteractionEndState( kind: 'approval' | 'question', 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'; } const TODO_LIST_TOOL_NAME = 'TodoList'; const TODO_ENTITY_ID = 'todo'; function todoWriteItems(input: unknown): TranscriptTodo['items'] | undefined { const todos = (input as { todos?: unknown } | undefined)?.todos; if (!Array.isArray(todos)) return undefined; const items: { title: string; status: 'pending' | 'in_progress' | 'done' }[] = []; for (const entry of todos) { const title = (entry as { title?: unknown } | undefined)?.title; const status = (entry as { status?: unknown } | undefined)?.status; if (typeof title !== 'string') return undefined; if (status !== 'pending' && status !== 'in_progress' && status !== 'done') return undefined; items.push({ title, status }); } return items; } function parseToolArgs(args: unknown): unknown { if (typeof args !== 'string' || args.length === 0) return args; try { return JSON.parse(args) as unknown; } catch { return args; } }