@deepstrike/wasm 0.2.50 → 0.2.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +2 -1
  2. package/dist/index.d.ts +4 -3
  3. package/dist/index.js +3 -2
  4. package/dist/runtime/canonical-kernel-step.d.ts +146 -0
  5. package/dist/runtime/canonical-kernel-step.js +1484 -0
  6. package/dist/runtime/execution-plane.d.ts +0 -3
  7. package/dist/runtime/execution-plane.js +0 -25
  8. package/dist/runtime/facade.js +3 -0
  9. package/dist/runtime/index.d.ts +7 -7
  10. package/dist/runtime/index.js +4 -3
  11. package/dist/runtime/kernel-event-log.d.ts +0 -1
  12. package/dist/runtime/kernel-event-log.js +7 -13
  13. package/dist/runtime/kernel-journal.d.ts +332 -0
  14. package/dist/runtime/kernel-journal.js +681 -0
  15. package/dist/runtime/kernel-step.d.ts +34 -89
  16. package/dist/runtime/kernel-step.js +67 -167
  17. package/dist/runtime/os-snapshot.d.ts +2 -2
  18. package/dist/runtime/os-snapshot.js +2 -6
  19. package/dist/runtime/payload-store.d.ts +30 -0
  20. package/dist/runtime/payload-store.js +66 -0
  21. package/dist/runtime/runner.d.ts +27 -75
  22. package/dist/runtime/runner.js +408 -632
  23. package/dist/runtime/session-log.d.ts +24 -27
  24. package/dist/runtime/session-log.js +11 -52
  25. package/dist/runtime/session-repair.d.ts +2 -36
  26. package/dist/runtime/session-repair.js +2 -47
  27. package/dist/runtime/sub-agent-orchestrator.d.ts +2 -2
  28. package/dist/runtime/sub-agent-orchestrator.js +36 -40
  29. package/dist/runtime/types/agent.d.ts +13 -16
  30. package/dist/runtime/types/agent.js +16 -41
  31. package/package.json +4 -3
  32. package/dist/runtime/kernel-rebuild.d.ts +0 -8
  33. package/dist/runtime/kernel-rebuild.js +0 -65
  34. package/dist/runtime/kernel-transaction-log.d.ts +0 -61
  35. package/dist/runtime/kernel-transaction-log.js +0 -140
  36. package/dist/runtime/large-result-spool.d.ts +0 -57
  37. package/dist/runtime/large-result-spool.js +0 -169
@@ -1,11 +1,7 @@
1
1
  import type { ProviderReplay, ToolCall, ToolErrorKind } from "../types.js";
2
2
  import type { MemoryRecall, MemoryScope } from "../memory/index.js";
3
3
  import type { KernelPrimitive } from "./kernel-event-log.js";
4
- import type { DurableAppendReceipt, KernelGenesisReceipt, KernelOperationGenesis, KernelTransaction } from "./kernel-transaction-log.js";
5
- export interface KernelTransactionEntry {
6
- log_seq: number;
7
- transaction: KernelTransaction;
8
- }
4
+ import type { KernelJournal } from "./kernel-journal.js";
9
5
  export type RollbackReason = {
10
6
  kind: "fatal_tool_error";
11
7
  tool_name: string;
@@ -98,14 +94,6 @@ export type SessionEvent = {
98
94
  kind: "page_in";
99
95
  turn: number;
100
96
  entry_count: number;
101
- } | {
102
- kind: "large_result_spooled";
103
- turn: number;
104
- call_id: string;
105
- tool: string;
106
- original_size: number;
107
- preview_size: number;
108
- spool_ref?: string;
109
97
  } | {
110
98
  kind: "rollbacked";
111
99
  turn: number;
@@ -204,7 +192,9 @@ export type SessionEvent = {
204
192
  kind: "agent_process_changed";
205
193
  turn: number;
206
194
  agent_id: string;
207
- parent_session_id: string;
195
+ parent_task_id?: string;
196
+ /** Host audit identity; canonical kernel observations never populate it. */
197
+ parent_session_id?: string;
208
198
  role: string;
209
199
  isolation: string;
210
200
  context_inheritance: string;
@@ -267,6 +257,11 @@ export type SessionEvent = {
267
257
  turn: number;
268
258
  node_outcomes: import("./types/agent.js").KernelWorkflowNodeOutcome[];
269
259
  total_nodes: number;
260
+ } | {
261
+ kind: "kernel_observation";
262
+ turn: number;
263
+ observation_kind: string;
264
+ raw: Record<string, unknown>;
270
265
  } | {
271
266
  kind: "run_terminal";
272
267
  reason: string;
@@ -277,6 +272,11 @@ export type SessionEvent = {
277
272
  compressed_seq: number;
278
273
  summary: string;
279
274
  };
275
+ /**
276
+ * The business-projection log (spec §9.2): run started/terminal, stream events, observations,
277
+ * provider/tool presentation, and audit metadata. Canonical durable records live exclusively in
278
+ * `KernelJournal` (spec §9.1).
279
+ */
280
280
  export interface SessionLog {
281
281
  append(sessionId: string, event: SessionEvent): Promise<number>;
282
282
  read(sessionId: string, fromSeq?: number, primitiveFilter?: KernelPrimitive): Promise<Array<{
@@ -284,18 +284,20 @@ export interface SessionLog {
284
284
  event: SessionEvent;
285
285
  }>>;
286
286
  latestSeq(sessionId: string): Promise<number>;
287
- appendKernelGenesis(sessionId: string, genesis: KernelOperationGenesis): Promise<KernelGenesisReceipt>;
288
- readKernelGenesis(sessionId: string, operationId: string): Promise<KernelOperationGenesis | undefined>;
289
- compareAndAppendKernelTransaction(sessionId: string, expectedTransactionHead: string, transaction: KernelTransaction): Promise<DurableAppendReceipt>;
290
- readKernelTransactions(sessionId: string, operationId: string, fromStepSeq?: number): Promise<KernelTransactionEntry[]>;
291
- kernelTransactionHead(sessionId: string, operationId: string): Promise<string | undefined>;
292
287
  }
288
+ /**
289
+ * **Single-isolate dev/test implementation** of both capabilities (spec §9.4: one class may
290
+ * implement several capabilities; the *interfaces* stay separate). Its `KernelJournal` half is
291
+ * `InMemoryKernelJournal`, whose CAS is atomic within one isolate only. A host whose journal must
292
+ * outlive its isolate — or be shared with another one — injects a `DriverKernelJournal` over a
293
+ * durable `JournalStorageDriver` instead.
294
+ */
293
295
  export declare class InMemorySessionLog implements SessionLog {
294
296
  private store;
297
+ /** Business event sequence space only — journal records number themselves by `step_seq`. */
295
298
  private seqCounters;
296
- private genesisStore;
297
- private transactionStore;
298
- private operationKey;
299
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
300
+ readonly kernelJournal: KernelJournal;
299
301
  private nextSeq;
300
302
  append(sessionId: string, event: SessionEvent): Promise<number>;
301
303
  read(sessionId: string, fromSeq?: number, primitiveFilter?: KernelPrimitive): Promise<Array<{
@@ -303,9 +305,4 @@ export declare class InMemorySessionLog implements SessionLog {
303
305
  event: SessionEvent;
304
306
  }>>;
305
307
  latestSeq(sessionId: string): Promise<number>;
306
- appendKernelGenesis(sessionId: string, genesis: KernelOperationGenesis): Promise<KernelGenesisReceipt>;
307
- readKernelGenesis(sessionId: string, operationId: string): Promise<KernelOperationGenesis | undefined>;
308
- compareAndAppendKernelTransaction(sessionId: string, expectedTransactionHead: string, transaction: KernelTransaction): Promise<DurableAppendReceipt>;
309
- readKernelTransactions(sessionId: string, operationId: string, fromStepSeq?: number): Promise<KernelTransactionEntry[]>;
310
- kernelTransactionHead(sessionId: string, operationId: string): Promise<string | undefined>;
311
308
  }
@@ -1,13 +1,18 @@
1
1
  import { primitiveForKind } from "./kernel-event-log.js";
2
- import { KernelLogConflictError, KernelLogIntegrityError, verifyKernelOperationGenesis, verifyKernelTransaction, verifyKernelTransactionSuccessor, } from "./kernel-transaction-log.js";
2
+ import { InMemoryKernelJournal } from "./kernel-journal.js";
3
+ /**
4
+ * **Single-isolate dev/test implementation** of both capabilities (spec §9.4: one class may
5
+ * implement several capabilities; the *interfaces* stay separate). Its `KernelJournal` half is
6
+ * `InMemoryKernelJournal`, whose CAS is atomic within one isolate only. A host whose journal must
7
+ * outlive its isolate — or be shared with another one — injects a `DriverKernelJournal` over a
8
+ * durable `JournalStorageDriver` instead.
9
+ */
3
10
  export class InMemorySessionLog {
4
11
  store = new Map();
12
+ /** Business event sequence space only — journal records number themselves by `step_seq`. */
5
13
  seqCounters = new Map();
6
- genesisStore = new Map();
7
- transactionStore = new Map();
8
- operationKey(sessionId, operationId) {
9
- return `${sessionId}\u0000${operationId}`;
10
- }
14
+ /** The durable transaction capability, held rather than inherited (spec §9.1/§9.4). */
15
+ kernelJournal = new InMemoryKernelJournal();
11
16
  nextSeq(sessionId) {
12
17
  const seq = this.seqCounters.get(sessionId) ?? 0;
13
18
  this.seqCounters.set(sessionId, seq + 1);
@@ -34,50 +39,4 @@ export class InMemorySessionLog {
34
39
  async latestSeq(sessionId) {
35
40
  return (this.seqCounters.get(sessionId) ?? 0) - 1;
36
41
  }
37
- async appendKernelGenesis(sessionId, genesis) {
38
- await verifyKernelOperationGenesis(genesis);
39
- const operationKey = this.operationKey(sessionId, genesis.operation_id);
40
- const existing = this.genesisStore.get(operationKey);
41
- if (existing) {
42
- if (existing.genesis.genesis_digest !== genesis.genesis_digest) {
43
- throw new KernelLogConflictError("session already has a different kernel operation genesis");
44
- }
45
- return { log_seq: existing.log_seq, genesis_digest: genesis.genesis_digest };
46
- }
47
- const log_seq = this.nextSeq(sessionId);
48
- this.genesisStore.set(operationKey, { log_seq, genesis });
49
- return { log_seq, genesis_digest: genesis.genesis_digest };
50
- }
51
- async readKernelGenesis(sessionId, operationId) {
52
- return this.genesisStore.get(this.operationKey(sessionId, operationId))?.genesis;
53
- }
54
- async compareAndAppendKernelTransaction(sessionId, expectedTransactionHead, transaction) {
55
- await verifyKernelTransaction(transaction);
56
- const operationKey = this.operationKey(sessionId, transaction.operation_id);
57
- const genesis = this.genesisStore.get(operationKey)?.genesis;
58
- if (!genesis)
59
- throw new KernelLogIntegrityError("kernel transaction requires a durable genesis");
60
- if (transaction.operation_id !== genesis.operation_id) {
61
- throw new KernelLogIntegrityError("kernel transaction operation_id does not match genesis");
62
- }
63
- const head = await this.kernelTransactionHead(sessionId, transaction.operation_id);
64
- if (head !== expectedTransactionHead || transaction.previous_transaction_digest !== head) {
65
- throw new KernelLogConflictError("kernel transaction head changed before compare-and-append");
66
- }
67
- const entries = this.transactionStore.get(operationKey) ?? [];
68
- verifyKernelTransactionSuccessor(entries.at(-1)?.transaction, transaction);
69
- const log_seq = this.nextSeq(sessionId);
70
- entries.push({ log_seq, transaction });
71
- this.transactionStore.set(operationKey, entries);
72
- return { log_seq, transaction_digest: transaction.transaction_digest };
73
- }
74
- async readKernelTransactions(sessionId, operationId, fromStepSeq = 1) {
75
- return (this.transactionStore.get(this.operationKey(sessionId, operationId)) ?? []).filter(entry => entry.transaction.step_seq >= fromStepSeq);
76
- }
77
- async kernelTransactionHead(sessionId, operationId) {
78
- const operationKey = this.operationKey(sessionId, operationId);
79
- const entries = this.transactionStore.get(operationKey) ?? [];
80
- return entries.at(-1)?.transaction.transaction_digest
81
- ?? this.genesisStore.get(operationKey)?.genesis.genesis_digest;
82
- }
83
42
  }
@@ -37,8 +37,7 @@ export declare function buildRunTerminalEvent(input: {
37
37
  }): Extract<SessionEvent, {
38
38
  kind: "run_terminal";
39
39
  }>;
40
- /** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
41
- * result-borne control signals + output so resume replays control flow and re-seeds outputs. */
40
+ /** Build the audit projection emitted after a workflow node finishes. */
42
41
  export declare function buildWorkflowNodeCompletedEvent(input: {
43
42
  turn: number;
44
43
  agentId: string;
@@ -51,29 +50,7 @@ export declare function buildWorkflowNodeCompletedEvent(input: {
51
50
  }): Extract<SessionEvent, {
52
51
  kind: "workflow_node_completed";
53
52
  }>;
54
- /** One recovered node completion: the agent id plus its persisted control signals and output. */
55
- export interface RecoveredNodeOutcome {
56
- agentId: string;
57
- status: WorkflowNodeStatus;
58
- termination: string;
59
- classifyBranch?: string;
60
- tournamentWinner?: string;
61
- loopContinue?: boolean;
62
- output?: Message;
63
- }
64
- /**
65
- * Recover completed workflow node records from a session event stream. Scans for
66
- * workflow_node_completed events with termination "completed" and returns them WITH their
67
- * result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
68
- * `resumed_outcomes` so a classifier re-prunes and a loop stop is honored, and re-seeds the
69
- * driver's outputs map from the persisted output text.
70
- */
71
- export declare function recoverWorkflowNodeOutcomes(events: Array<{
72
- seq: number;
73
- event: SessionEvent;
74
- }>): RecoveredNodeOutcome[];
75
- /** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
76
- * re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
53
+ /** Build the audit projection emitted after a runtime workflow submission. */
77
54
  export declare function buildWorkflowNodesSubmittedEvent(input: {
78
55
  turn: number;
79
56
  nodes: Record<string, unknown>[];
@@ -82,14 +59,3 @@ export declare function buildWorkflowNodesSubmittedEvent(input: {
82
59
  }): Extract<SessionEvent, {
83
60
  kind: "workflow_nodes_submitted";
84
61
  }>;
85
- /** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
86
- * resumeWorkflow, so dynamically-appended nodes are reconstructed.
87
- * `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
88
- export declare function recoverSubmittedWorkflowNodes(events: Array<{
89
- seq: number;
90
- event: SessionEvent;
91
- }>): {
92
- submissions: Record<string, unknown>[][];
93
- bases: number[];
94
- submitters: Array<string | undefined>;
95
- };
@@ -48,8 +48,7 @@ export function buildRunTerminalEvent(input) {
48
48
  total_tokens: Math.max(0, input.totalTokens),
49
49
  };
50
50
  }
51
- /** Build workflow_node_completed for persistence after a node finishes. W-1: carries the
52
- * result-borne control signals + output so resume replays control flow and re-seeds outputs. */
51
+ /** Build the audit projection emitted after a workflow node finishes. */
53
52
  export function buildWorkflowNodeCompletedEvent(input) {
54
53
  return {
55
54
  kind: "workflow_node_completed",
@@ -63,32 +62,7 @@ export function buildWorkflowNodeCompletedEvent(input) {
63
62
  ...(input.output ? { output: input.output } : {}),
64
63
  };
65
64
  }
66
- /**
67
- * Recover completed workflow node records from a session event stream. Scans for
68
- * workflow_node_completed events with termination "completed" and returns them WITH their
69
- * result-borne control signals (W-1) — resumeWorkflow lowers these to the kernel's
70
- * `resumed_outcomes` so a classifier re-prunes and a loop stop is honored, and re-seeds the
71
- * driver's outputs map from the persisted output text.
72
- */
73
- export function recoverWorkflowNodeOutcomes(events) {
74
- const completed = [];
75
- for (const { event } of events) {
76
- if (event.kind === "workflow_node_completed") {
77
- completed.push({
78
- agentId: event.agent_id,
79
- status: event.status,
80
- termination: event.termination,
81
- ...(event.classify_branch !== undefined ? { classifyBranch: event.classify_branch } : {}),
82
- ...(event.tournament_winner !== undefined ? { tournamentWinner: event.tournament_winner } : {}),
83
- ...(event.loop_continue !== undefined ? { loopContinue: event.loop_continue } : {}),
84
- ...(event.output !== undefined ? { output: event.output } : {}),
85
- });
86
- }
87
- }
88
- return completed;
89
- }
90
- /** R3-1: build workflow_nodes_submitted for persistence after a runtime submission, so resume can
91
- * re-apply it. `nodes` is the kernel-shape (snake_case) submitted node array. */
65
+ /** Build the audit projection emitted after a runtime workflow submission. */
92
66
  export function buildWorkflowNodesSubmittedEvent(input) {
93
67
  return {
94
68
  kind: "workflow_nodes_submitted",
@@ -98,22 +72,3 @@ export function buildWorkflowNodesSubmittedEvent(input) {
98
72
  ...(input.submitterAgentId !== undefined ? { submitter_agent_id: input.submitterAgentId } : {}),
99
73
  };
100
74
  }
101
- /** R3-1: recover the runtime submission batches (in order) to rebuild `resumed_submissions` for
102
- * resumeWorkflow, so dynamically-appended nodes are reconstructed.
103
- * `submitters` is parallel to `submissions` (undefined = host/bootstrap submission). */
104
- export function recoverSubmittedWorkflowNodes(events) {
105
- const submissions = [];
106
- const bases = [];
107
- const submitters = [];
108
- for (const { event } of events) {
109
- if (event.kind === "workflow_nodes_submitted") {
110
- submissions.push(event.nodes);
111
- submitters.push(event.submitter_agent_id);
112
- if (event.base_index === undefined) {
113
- throw new Error("workflow_nodes_submitted is missing required base_index");
114
- }
115
- bases.push(event.base_index);
116
- }
117
- }
118
- return { submissions, bases, submitters };
119
- }
@@ -39,5 +39,5 @@ export declare class SubAgentOrchestrator {
39
39
  run(ctx: SubAgentRunContext): Promise<SubAgentResult>;
40
40
  }
41
41
  export declare const defaultSubAgentOrchestrator: SubAgentOrchestrator;
42
- /** Kernel spawn without an active parent run loop (harness / coordinator use). */
43
- export declare function spawnStandalone(parentOpts: RuntimeOptions, parentSessionId: string, spec: AgentRunSpec, orchestrator?: SubAgentOrchestrator): Promise<SubAgentResult>;
42
+ /** Canonical single-node root workflow for harness / coordinator use. */
43
+ export declare function spawnStandalone(parentOpts: RuntimeOptions, parentSessionId: string, spec: AgentRunSpec, orchestrator?: SubAgentOrchestrator, contextInput?: string): Promise<SubAgentResult>;
@@ -1,6 +1,4 @@
1
- import { agentRunSpecToKernel, findSpawnProcessObservation, spawnObservationToManifest } from "./types/agent.js";
2
1
  import { FilteredExecutionPlane } from "./filtered-plane.js";
3
- import { kernelApply } from "./kernel-step.js";
4
2
  /** #2-B-ii: bridge a parent AbortSignal to a child runner's `interrupt()` (fires now if already aborted). */
5
3
  function linkAbort(signal, runner) {
6
4
  if (!signal)
@@ -121,8 +119,6 @@ export class SubAgentOrchestrator {
121
119
  dreamStore: metaTools.has("memory") ? ctx.parentOpts.dreamStore : undefined,
122
120
  knowledgeSource: metaTools.has("knowledge") ? ctx.parentOpts.knowledgeSource : undefined,
123
121
  enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
124
- // M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
125
- isWorkflowNode: ctx.isWorkflowNode,
126
122
  // The child runs under ITS OWN spec, never the parent's: the spread above would otherwise
127
123
  // leak the parent's `runSpec` (identity, capability filter — and an armed `loopRound`,
128
124
  // giving every child a phantom pace tool). A loop-node iteration carries its own minimal
@@ -167,42 +163,42 @@ export class SubAgentOrchestrator {
167
163
  }
168
164
  }
169
165
  export const defaultSubAgentOrchestrator = new SubAgentOrchestrator();
170
- /** Kernel spawn without an active parent run loop (harness / coordinator use). */
171
- export async function spawnStandalone(parentOpts, parentSessionId, spec, orchestrator = defaultSubAgentOrchestrator) {
172
- const kernel = await (await import("./kernel.js")).getKernel();
173
- const runtime = new kernel.KernelRuntime({
174
- maxTokens: parentOpts.maxTokens,
175
- maxTurns: parentOpts.maxTurns ?? 25,
176
- timeoutMs: parentOpts.timeoutMs,
177
- });
178
- const pending = [];
179
- kernelApply(runtime, pending, { kind: "start_run", task: { goal: "coordinator", criteria: [] } });
180
- const observations = kernelApply(runtime, pending, {
181
- kind: "spawn_sub_agent",
182
- spec: agentRunSpecToKernel(spec),
183
- parent_session_id: parentSessionId,
184
- });
185
- const spawned = findSpawnProcessObservation(observations);
186
- if (!spawned) {
187
- throw new Error("spawn_sub_agent did not emit agent_process_changed");
166
+ /** Canonical single-node root workflow for harness / coordinator use. */
167
+ export async function spawnStandalone(parentOpts, parentSessionId, spec, orchestrator = defaultSubAgentOrchestrator, contextInput) {
168
+ if (spec.tokenBudget !== undefined || spec.maxTurns !== undefined || spec.maxWallMs !== undefined) {
169
+ throw new Error("spawnStandalone cannot represent per-node resource caps under canonical ABI v3");
188
170
  }
189
- const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
190
- await parentOpts.sessionLog.append(parentSessionId, {
191
- kind: "agent_process_changed",
192
- turn: manifest.turn ?? 0,
193
- agent_id: manifest.agent_id,
194
- parent_session_id: manifest.parent_session_id,
195
- role: manifest.role,
196
- isolation: manifest.isolation,
197
- context_inheritance: manifest.context_inheritance,
198
- state: "running",
199
- permitted_capability_ids: manifest.permitted_capability_ids ?? [],
200
- });
201
- return orchestrator.run({
202
- parentOpts,
203
- parentSessionId,
204
- spec,
205
- manifest,
206
- sessionLog: parentOpts.sessionLog,
171
+ const { RuntimeRunner } = await import("./runner.js");
172
+ let captured;
173
+ const bridge = {
174
+ async run(ctx) {
175
+ const child = await orchestrator.run({
176
+ ...ctx,
177
+ parentSessionId,
178
+ spec,
179
+ manifest: {
180
+ ...ctx.manifest,
181
+ agent_id: spec.identity.agentId,
182
+ parent_session_id: parentSessionId,
183
+ },
184
+ ...(contextInput ? { contextInput } : {}),
185
+ });
186
+ captured = child;
187
+ return { ...child, agentId: ctx.manifest.agent_id };
188
+ },
189
+ };
190
+ const runner = new RuntimeRunner({ ...parentOpts, subAgentOrchestrator: bridge });
191
+ const outcome = await runner.runWorkflow({
192
+ nodes: [{
193
+ task: spec.goal,
194
+ role: spec.role,
195
+ isolation: spec.isolation,
196
+ ...(spec.modelHint ? { modelHint: spec.modelHint } : {}),
197
+ }],
207
198
  });
199
+ if (outcome.rejection)
200
+ throw new Error(`canonical standalone spawn rejected: ${outcome.rejection.reason}`);
201
+ if (!captured)
202
+ throw new Error("canonical standalone spawn completed without a child result");
203
+ return captured;
208
204
  }
@@ -79,12 +79,6 @@ export interface AgentProcessChangedObservation {
79
79
  permitted_capability_ids?: string[];
80
80
  result_termination?: string;
81
81
  }
82
- /** Map kernel spawn observation → host manifest. */
83
- export declare function spawnObservationToManifest(obs: AgentProcessChangedObservation | Record<string, unknown>, spec: AgentRunSpec, parentSessionId: string): AgentProcessChangedObservation;
84
- export declare function findSpawnProcessObservation(observations: Array<{
85
- kind: string;
86
- agent_id?: string;
87
- }>): AgentProcessChangedObservation | undefined;
88
82
  export interface LoopResult {
89
83
  termination: TerminationReason | string;
90
84
  finalMessage?: Message;
@@ -133,6 +127,18 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
133
127
  export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
134
128
  export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
135
129
  export declare function milestoneCheckFail(phaseId: string, reason: string): MilestoneCheckResult;
130
+ /**
131
+ * R-B27: the conservative resolution the runner feeds back when an `evaluate_milestone` effect
132
+ * arrives with no phase `verifier` and no host `onMilestoneEvaluate` hook. Nothing can attest the
133
+ * phase, but the kernel is already holding the effect in its pending table — returning without an
134
+ * answer leaves a dangling effect that a logical-checkpoint recovery cannot resolve. The current
135
+ * `MilestoneResult` wire has no error field, so "could not be verified" is expressed with the
136
+ * shape the wire does have: `passed: false` (the phase does not advance — fail-closed).
137
+ *
138
+ * The string is part of the cross-SDK contract: Node (`node/src/types/agent.ts`),
139
+ * Python (`deepstrike/types/agent.py`) and Rust must feed back the byte-identical reason.
140
+ */
141
+ export declare const MILESTONE_UNVERIFIED_REASON = "milestone unverified: no verifier configured and no host evaluation hook (fail-closed)";
136
142
  /** A task for a workflow node: a full object, or a bare goal string. */
137
143
  export type WorkflowTaskSpec = {
138
144
  goal: string;
@@ -275,19 +281,10 @@ export interface WorkflowBudget {
275
281
  }
276
282
  /** G4: a concise budget note appended to a coordinator node's goal. "" when nothing is bounded. */
277
283
  export declare function workflowBudgetNote(budget: WorkflowBudget | undefined): string;
278
- /** Map one host `WorkflowNodeSpec` to its snake_case kernel JSON. Shared by `load_workflow` and
284
+ /** Map one host `WorkflowNodeSpec` to its snake_case canonical JSON. Shared by the workflow root and
279
285
  * `submit_workflow_nodes` (R3-1) so the two encodings never drift. */
280
286
  export declare function workflowNodeSpecToKernel(n: WorkflowNodeSpec): Record<string, unknown>;
281
287
  export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
282
- /** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
283
- * `submitterAgentId` so the kernel enforces no-privilege-escalation (quarantined submitter ⇒ its
284
- * nodes coerced to quarantined). Omitted ⇒ no coercion. */
285
- export declare function submitWorkflowNodesToKernel(nodes: WorkflowNodeSpec[], submitterAgentId?: string): Record<string, unknown>;
286
- /** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
287
- * `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
288
- * `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
289
- * on the flatten case. */
290
- export declare function submitWorkflowToKernel(spec: WorkflowSpec, parentSessionId: string, submitterAgentId?: string): Record<string, unknown>;
291
288
  export declare const submitWorkflowNodesTool: ToolSchema;
292
289
  /** M5 v1 (flatten): the tool an agent calls to author a sub-workflow — a cohesive DAG of nodes
293
290
  * composed onto the running workflow. Lowers to the same append path as `submit_workflow_nodes`
@@ -1,22 +1,4 @@
1
1
  import { getKernel } from "../kernel.js";
2
- /** Map kernel spawn observation → host manifest. */
3
- export function spawnObservationToManifest(obs, spec, parentSessionId) {
4
- const o = obs;
5
- return {
6
- kind: "agent_process_changed",
7
- turn: o.turn,
8
- agent_id: String(o.agent_id ?? spec.identity.agentId),
9
- parent_session_id: String(o.parent_session_id ?? parentSessionId),
10
- role: String(o.role ?? spec.role),
11
- isolation: String(o.isolation ?? spec.isolation ?? "shared"),
12
- context_inheritance: String(o.context_inheritance ?? "none"),
13
- permitted_capability_ids: o.permitted_capability_ids ?? [],
14
- };
15
- }
16
- export function findSpawnProcessObservation(observations) {
17
- const hit = observations.find(o => o.kind === "agent_process_changed" && typeof o.agent_id === "string");
18
- return hit;
19
- }
20
2
  export function agentIdentitySub(agentId, sessionId, parentSessionId) {
21
3
  return {
22
4
  agentId,
@@ -96,6 +78,9 @@ export function subAgentResultToKernel(result) {
96
78
  const finalMessage = result.result.finalMessage;
97
79
  return {
98
80
  agent_id: result.agentId,
81
+ ...(result.submittedNodes?.length
82
+ ? { submitted_nodes: result.submittedNodes.map(workflowNodeSpecToKernel) }
83
+ : {}),
99
84
  result: {
100
85
  termination: result.result.termination,
101
86
  final_message: finalMessage
@@ -126,6 +111,18 @@ export function milestoneCheckPass(phaseId) {
126
111
  export function milestoneCheckFail(phaseId, reason) {
127
112
  return { phaseId, passed: false, reason };
128
113
  }
114
+ /**
115
+ * R-B27: the conservative resolution the runner feeds back when an `evaluate_milestone` effect
116
+ * arrives with no phase `verifier` and no host `onMilestoneEvaluate` hook. Nothing can attest the
117
+ * phase, but the kernel is already holding the effect in its pending table — returning without an
118
+ * answer leaves a dangling effect that a logical-checkpoint recovery cannot resolve. The current
119
+ * `MilestoneResult` wire has no error field, so "could not be verified" is expressed with the
120
+ * shape the wire does have: `passed: false` (the phase does not advance — fail-closed).
121
+ *
122
+ * The string is part of the cross-SDK contract: Node (`node/src/types/agent.ts`),
123
+ * Python (`deepstrike/types/agent.py`) and Rust must feed back the byte-identical reason.
124
+ */
125
+ export const MILESTONE_UNVERIFIED_REASON = "milestone unverified: no verifier configured and no host evaluation hook (fail-closed)";
129
126
  export function workflowNodeStatusFromTermination(termination) {
130
127
  if (termination === "completed")
131
128
  return "completed";
@@ -202,7 +199,7 @@ function nodeKindToKernel(n) {
202
199
  return { type: "tournament", entrants: n.tournament.entrants.map(workflowTaskToKernel) };
203
200
  return undefined;
204
201
  }
205
- /** Map one host `WorkflowNodeSpec` to its snake_case kernel JSON. Shared by `load_workflow` and
202
+ /** Map one host `WorkflowNodeSpec` to its snake_case canonical JSON. Shared by the workflow root and
206
203
  * `submit_workflow_nodes` (R3-1) so the two encodings never drift. */
207
204
  export function workflowNodeSpecToKernel(n) {
208
205
  const kind = nodeKindToKernel(n);
@@ -228,28 +225,6 @@ export function workflowNodeSpecToKernel(n) {
228
225
  export function workflowSpecToKernel(spec) {
229
226
  return { nodes: spec.nodes.map(workflowNodeSpecToKernel) };
230
227
  }
231
- /** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
232
- * `submitterAgentId` so the kernel enforces no-privilege-escalation (quarantined submitter ⇒ its
233
- * nodes coerced to quarantined). Omitted ⇒ no coercion. */
234
- export function submitWorkflowNodesToKernel(nodes, submitterAgentId) {
235
- return {
236
- kind: "submit_workflow_nodes",
237
- nodes: nodes.map(workflowNodeSpecToKernel),
238
- ...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
239
- };
240
- }
241
- /** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
242
- * `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
243
- * `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
244
- * on the flatten case. */
245
- export function submitWorkflowToKernel(spec, parentSessionId, submitterAgentId) {
246
- return {
247
- kind: "submit_workflow",
248
- spec: workflowSpecToKernel(spec),
249
- parent_session_id: parentSessionId,
250
- ...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
251
- };
252
- }
253
228
  /** R3-1: the tool a workflow-coordinator node's agent calls to append work to the running DAG. */
254
229
  /** Shared JSON-Schema for a workflow-node batch (a DAG). Used by both `submit_workflow_nodes`
255
230
  * (append) and `start_workflow` (M5 v1: author a sub-workflow), so the two tools never drift. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.50",
3
+ "version": "0.2.51",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,10 +12,11 @@
12
12
  "scripts": {
13
13
  "build:wasm": "wasm-pack build ../crates/deepstrike-wasm --target bundler --out-dir ../../wasm/pkg",
14
14
  "build": "tsc",
15
- "test": "node --experimental-vm-modules node_modules/.bin/jest"
15
+ "test": "node --experimental-vm-modules node_modules/.bin/jest",
16
+ "test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs"
16
17
  },
17
18
  "dependencies": {
18
- "@deepstrike/wasm-kernel": "0.2.50"
19
+ "@deepstrike/wasm-kernel": "0.2.51"
19
20
  },
20
21
  "devDependencies": {
21
22
  "@types/jest": "^30.0.0",
@@ -1,8 +0,0 @@
1
- import type { KernelRuntimeHandle } from "./kernel-step.js";
2
- import { type KernelOperationCursor, type KernelOperationGenesis, type KernelTransaction } from "./kernel-transaction-log.js";
3
- export interface KernelRebuildResult {
4
- runtime: KernelRuntimeHandle;
5
- cursor: KernelOperationCursor;
6
- }
7
- /** Deterministically fold an authoritative stream into a fresh WASM runtime. */
8
- export declare function rebuildKernelRuntime(runtime: KernelRuntimeHandle, genesis: KernelOperationGenesis, transactions: readonly KernelTransaction[]): Promise<KernelRebuildResult>;
@@ -1,65 +0,0 @@
1
- import { KernelLogIntegrityError, kernelRecordDigest, verifyKernelTransactionStream, } from "./kernel-transaction-log.js";
2
- /** Deterministically fold an authoritative stream into a fresh WASM runtime. */
3
- export async function rebuildKernelRuntime(runtime, genesis, transactions) {
4
- const cursor = await verifyKernelTransactionStream(genesis, transactions);
5
- await assertFreshRuntimeMatchesGenesis(runtime, genesis);
6
- for (const transaction of transactions) {
7
- const prepared = JSON.parse(runtime.prepareStep(JSON.stringify(transaction.input)));
8
- const token = prepared.prepare_token;
9
- try {
10
- if (prepared.status !== "prepared" || !token) {
11
- throw new KernelLogIntegrityError(`kernel rebuild step ${transaction.step_seq} was not accepted as a new transition`);
12
- }
13
- if (prepared.base_generation !== transaction.base_generation) {
14
- throw new KernelLogIntegrityError(`kernel rebuild generation diverged at step ${transaction.step_seq}`);
15
- }
16
- if (prepared.step.step_seq !== transaction.step_seq) {
17
- throw new KernelLogIntegrityError(`kernel rebuild step_seq diverged at step ${transaction.step_seq}`);
18
- }
19
- if (await kernelRecordDigest(prepared.input) !== transaction.input_digest) {
20
- throw new KernelLogIntegrityError(`kernel rebuild normalized input diverged at step ${transaction.step_seq}`);
21
- }
22
- if (await kernelRecordDigest(prepared.step) !== transaction.step_digest) {
23
- throw new KernelLogIntegrityError(`kernel rebuild planned step diverged at step ${transaction.step_seq}`);
24
- }
25
- const committed = JSON.parse(runtime.commitPrepared(token));
26
- if (await kernelRecordDigest(committed) !== transaction.step_digest) {
27
- throw new KernelLogIntegrityError(`kernel rebuild committed step diverged at step ${transaction.step_seq}`);
28
- }
29
- }
30
- catch (error) {
31
- if (token && prepared.status === "prepared") {
32
- try {
33
- runtime.abortPrepared(token);
34
- }
35
- catch { /* discard failed runtime */ }
36
- }
37
- throw error;
38
- }
39
- }
40
- return { runtime, cursor };
41
- }
42
- async function assertFreshRuntimeMatchesGenesis(runtime, genesis) {
43
- const snapshot = JSON.parse(runtime.snapshot());
44
- if (snapshot.abi_version !== genesis.abi_version) {
45
- throw new KernelLogIntegrityError("kernel runtime ABI version does not match operation genesis");
46
- }
47
- if (genesis.default_policy_version !== 1) {
48
- throw new KernelLogIntegrityError("kernel operation uses an unsupported default policy version");
49
- }
50
- if (snapshot.operation_id || snapshot.next_step_seq !== 1 || snapshot.accepted_inputs.length !== 0) {
51
- throw new KernelLogIntegrityError("kernel rebuild requires a fresh runtime");
52
- }
53
- if (await kernelRecordDigest(snapshot.initial_policy) !== await kernelRecordDigest(genesis.initial_scheduler_policy)) {
54
- throw new KernelLogIntegrityError("kernel runtime initial policy does not match operation genesis");
55
- }
56
- const defaults = {
57
- snapshot_version: snapshot.snapshot_version,
58
- snapshot_input_limit: snapshot.snapshot_input_limit,
59
- max_input_bytes: snapshot.max_input_bytes,
60
- snapshot_journal_bytes_limit: snapshot.snapshot_journal_bytes_limit,
61
- };
62
- if (await kernelRecordDigest(defaults) !== await kernelRecordDigest(genesis.resolved_runtime_defaults)) {
63
- throw new KernelLogIntegrityError("kernel runtime defaults do not match operation genesis");
64
- }
65
- }