import { workflowPromptForRun } from "./prompt.ts"; import { addQuestion, answerQuestion, createActiveRun, openBlockingQuestions, readRun, recordRunContext, reviseFact, } from "./state.ts"; import { readLedgerEvents, type KdLedgerEvent } from "./ledger.ts"; import { recordInputReceived, recordToolBlocked, recordToolSucceeded } from "./observation.ts"; import type { ActiveRun, KdContextEntry, KdHarnessMode } from "./types.ts"; export type KdTranscriptStep = | { kind: "input"; text: string; source?: string } | { kind: "context"; text: string; contextKind?: KdContextEntry["kind"]; sourceRefs?: string[] } | { kind: "ask"; question: string; reason?: string; contextSummary?: string; sourceRefs?: string[]; factLabel?: string; choices?: string[]; } | { kind: "answer"; id?: string; answer: string } | { kind: "revise"; factLabel: string; value: string; reason?: string } | { kind: "tool-block"; toolName: string; reason: string; path?: string } | { kind: "tool-success"; toolName: string; summary: string; path?: string; sourceRefs?: string[] } | { kind: "prompt"; text: string }; export interface KdTranscriptScenario { id: string; goal: string; product?: string; version?: string; mode?: KdHarnessMode; steps: KdTranscriptStep[]; } export interface KdTranscriptReplay { run: ActiveRun; prompts: string[]; events: KdLedgerEvent[]; } export function replayTranscriptScenario(cwd: string, scenario: KdTranscriptScenario): KdTranscriptReplay { let run = createActiveRun(cwd, scenario.goal, scenario.product, scenario.version, scenario.mode); const prompts: string[] = []; for (const step of scenario.steps) { run = readRun(cwd, run.id) ?? run; if (step.kind === "input") { recordInputReceived(cwd, run, { text: step.text, source: step.source }); if (step.text.trim() && !step.text.trim().startsWith("/")) { recordRunContext(cwd, run, { text: step.text, kind: "user-input" }); } continue; } if (step.kind === "context") { recordRunContext(cwd, run, { text: step.text, kind: step.contextKind, sourceRefs: step.sourceRefs }); continue; } if (step.kind === "ask") { addQuestion(cwd, run, { question: step.question, reason: step.reason, contextSummary: step.contextSummary, sourceRefs: step.sourceRefs, factLabel: step.factLabel, choices: step.choices, }); continue; } if (step.kind === "answer") { const id = step.id ?? openBlockingQuestions(run)[0]?.id; if (id) answerQuestion(cwd, run, id, step.answer); continue; } if (step.kind === "revise") { reviseFact(cwd, run, { factLabel: step.factLabel, value: step.value, reason: step.reason }); continue; } if (step.kind === "tool-block") { recordToolBlocked(cwd, run, { toolName: step.toolName, reason: step.reason, path: step.path }); continue; } if (step.kind === "tool-success") { recordToolSucceeded(cwd, run, { toolName: step.toolName, summary: step.summary, path: step.path, sourceRefs: step.sourceRefs, kind: "search" }); continue; } prompts.push(workflowPromptForRun(cwd, run, step.text)); } run = readRun(cwd, run.id) ?? run; return { run, prompts, events: readLedgerEvents(cwd, run), }; } export function transcriptScenarioFromLedger(input: { id: string; goal: string; product?: string; events: KdLedgerEvent[]; }): KdTranscriptScenario { const steps: KdTranscriptStep[] = []; for (const event of input.events) { if (event.type === "input.received") { steps.push({ kind: "input", text: event.summary, source: typeof event.data?.source === "string" ? event.data.source : undefined }); continue; } if (event.type === "context.recorded") { steps.push({ kind: "context", text: event.summary }); continue; } if (event.type === "question.asked") { steps.push({ kind: "ask", question: event.summary, factLabel: typeof event.data?.factLabel === "string" ? event.data.factLabel : undefined, sourceRefs: Array.isArray(event.data?.sourceRefs) ? event.data.sourceRefs.filter((item): item is string => typeof item === "string") : undefined, contextSummary: "由 ledger replay 生成;完整上下文读取原 run artifacts 和 ledger。", }); continue; } if (event.type === "question.answered") { const answer = typeof event.data?.answer === "string" ? event.data.answer : event.summary; steps.push({ kind: "answer", id: typeof event.data?.id === "string" ? event.data.id : undefined, answer }); continue; } if (event.type === "fact.revised") { const [factLabel, ...valueParts] = event.summary.split(":"); steps.push({ kind: "revise", factLabel, value: valueParts.join(":") || event.summary, reason: "由 ledger replay 生成。" }); continue; } if (event.type === "tool.blocked") { steps.push({ kind: "tool-block", toolName: typeof event.data?.toolName === "string" ? event.data.toolName : "unknown", path: typeof event.data?.path === "string" ? event.data.path : undefined, reason: event.summary, }); continue; } if (event.type === "tool.succeeded") { steps.push({ kind: "tool-success", toolName: typeof event.data?.toolName === "string" ? event.data.toolName : "unknown", path: typeof event.data?.path === "string" ? event.data.path : undefined, sourceRefs: Array.isArray(event.data?.sourceRefs) ? event.data.sourceRefs.filter((item): item is string => typeof item === "string") : undefined, summary: event.summary, }); } } return { id: input.id, goal: input.goal, product: input.product, steps }; }