/** * 本地开发宿主 —— mock 工具表 + 模拟工位(工单 / 卡)+ 回放夹具(编码规范 §8.2)。 * * 「系统外对着声明开发(本地 mock)」:开发者不必起系统就能跑活动; * 夹具录下的出入参可直接喂给回放审(§7.3 两缝的同一副夹具)。 */ import type { ActivityBase, ContractSpec } from './base.ts'; import type { ActivityDefinition, ActivityOutcome } from './define.ts'; import type { ChatCardDraft, LedgerKey, LedgerRow, ViewBlockLike, WorkOrderDraft } from './types.ts'; export type HostRecording = { readonly views: ViewBlockLike[][]; readonly htmls: string[]; readonly says: string[]; readonly workOrders: WorkOrderDraft[]; readonly cards: ChatCardDraft[]; readonly toolCalls: Array<{ tool: string; input: Record }>; readonly intents: Array<{ tool: string; input: Record; label: string }>; readonly listeners: Array<{ eventType: string; keyFilter: Record; deadline: string }>; readonly llmCalls: Array<{ model: string; prompt: string }>; readonly writes: Array<{ address: string; key: LedgerKey; patch: LedgerRow }>; }; export type HostOptions = { readonly contract: ContractSpec; /** mock 工具表:工具名 → 固定出参 */ readonly tools?: Readonly) => Record>>; /** mock 模型:模型名 → 固定出参 */ readonly models?: Readonly { text: string; object?: unknown }>>; /** 台账初值:地址 → 行 */ readonly ledger?: Readonly>; readonly memory?: ReadonlyArray<{ layer: 'fact' | 'pattern' | 'verdict' | 'exception'; statement: string }>; }; export type MockHost = { readonly base: ActivityBase; readonly recording: HostRecording; run(definition: ActivityDefinition): Promise; }; export function createMockHost(options: HostOptions): MockHost { const recording: HostRecording = { views: [], htmls: [], says: [], workOrders: [], cards: [], toolCalls: [], intents: [], listeners: [], llmCalls: [], writes: [], }; const ledger = new Map(Object.entries(options.ledger ?? {})); let cardSeq = 0; const base: ActivityBase = { contract: options.contract, interaction: { async pushView(blocks) { recording.views.push([...blocks]); }, async pushHtml(html) { if (/<\s*script\b/i.test(html)) throw new Error('视图模版不得含