@deepstrike/wasm 0.2.49 → 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 +72 -79
  22. package/dist/runtime/runner.js +424 -637
  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 +21 -16
  30. package/dist/runtime/types/agent.js +21 -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
  }
@@ -41,6 +41,14 @@ export interface AgentRunSpec {
41
41
  /** ③ loop-agent rounds: presence makes this run ONE round of a paced loop (gates the
42
42
  * kernel `pace` meta-tool and arms the pacing trap). */
43
43
  loopRound?: LoopRoundSpec;
44
+ /** Exposure baseline — the PRE-ACTIVATION tool surface *under* the `capabilityFilter` ceiling.
45
+ * The ceiling bounds what this run may EVER expose; the baseline selects which of those are
46
+ * advertised before any skill activates, so `exposed = meta ∪ ((baseline ∪ stableCore ∪
47
+ * ⋃ activeSkills.allowed_tools) ∩ ceiling)`. That makes narrow→wide progressive disclosure
48
+ * expressible. Absent ⇒ legacy behavior. `[]` is meaningful and distinct from absent: the minimal
49
+ * surface (meta-tools + stable-core only). Entries outside the ceiling silently intersect away.
50
+ * Lowered from `RuntimeOptions.baselineToolIds`. */
51
+ exposureBaseline?: string[];
44
52
  /** M1/G3: per-agent model preference; the host resolves it via `RuntimeOptions.providerFor`.
45
53
  * Host-side routing only — not sent to the kernel. */
46
54
  modelHint?: string;
@@ -71,12 +79,6 @@ export interface AgentProcessChangedObservation {
71
79
  permitted_capability_ids?: string[];
72
80
  result_termination?: string;
73
81
  }
74
- /** Map kernel spawn observation → host manifest. */
75
- export declare function spawnObservationToManifest(obs: AgentProcessChangedObservation | Record<string, unknown>, spec: AgentRunSpec, parentSessionId: string): AgentProcessChangedObservation;
76
- export declare function findSpawnProcessObservation(observations: Array<{
77
- kind: string;
78
- agent_id?: string;
79
- }>): AgentProcessChangedObservation | undefined;
80
82
  export interface LoopResult {
81
83
  termination: TerminationReason | string;
82
84
  finalMessage?: Message;
@@ -125,6 +127,18 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
125
127
  export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
126
128
  export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
127
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)";
128
142
  /** A task for a workflow node: a full object, or a bare goal string. */
129
143
  export type WorkflowTaskSpec = {
130
144
  goal: string;
@@ -267,19 +281,10 @@ export interface WorkflowBudget {
267
281
  }
268
282
  /** G4: a concise budget note appended to a coordinator node's goal. "" when nothing is bounded. */
269
283
  export declare function workflowBudgetNote(budget: WorkflowBudget | undefined): string;
270
- /** 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
271
285
  * `submit_workflow_nodes` (R3-1) so the two encodings never drift. */
272
286
  export declare function workflowNodeSpecToKernel(n: WorkflowNodeSpec): Record<string, unknown>;
273
287
  export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
274
- /** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
275
- * `submitterAgentId` so the kernel enforces no-privilege-escalation (quarantined submitter ⇒ its
276
- * nodes coerced to quarantined). Omitted ⇒ no coercion. */
277
- export declare function submitWorkflowNodesToKernel(nodes: WorkflowNodeSpec[], submitterAgentId?: string): Record<string, unknown>;
278
- /** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
279
- * `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
280
- * `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
281
- * on the flatten case. */
282
- export declare function submitWorkflowToKernel(spec: WorkflowSpec, parentSessionId: string, submitterAgentId?: string): Record<string, unknown>;
283
288
  export declare const submitWorkflowNodesTool: ToolSchema;
284
289
  /** M5 v1 (flatten): the tool an agent calls to author a sub-workflow — a cohesive DAG of nodes
285
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,
@@ -54,6 +36,11 @@ export function agentRunSpecToKernel(spec) {
54
36
  ...(spec.loopRound.defaultAction !== undefined ? { default_action: spec.loopRound.defaultAction } : {}),
55
37
  };
56
38
  }
39
+ // Exposure baseline: `undefined` ⇒ omit the field entirely (kernel `None` = legacy behavior);
40
+ // `[]` ⇒ send `[]` (kernel `Some([])` = the minimal surface). The unset/minimal distinction is
41
+ // load-bearing, so this is deliberately NOT the `length > 0` idiom `allowedToolIds` uses.
42
+ if (spec.exposureBaseline !== undefined)
43
+ out.exposure_baseline = [...spec.exposureBaseline];
57
44
  return out;
58
45
  }
59
46
  export function milestoneContractToKernel(contract) {
@@ -91,6 +78,9 @@ export function subAgentResultToKernel(result) {
91
78
  const finalMessage = result.result.finalMessage;
92
79
  return {
93
80
  agent_id: result.agentId,
81
+ ...(result.submittedNodes?.length
82
+ ? { submitted_nodes: result.submittedNodes.map(workflowNodeSpecToKernel) }
83
+ : {}),
94
84
  result: {
95
85
  termination: result.result.termination,
96
86
  final_message: finalMessage
@@ -121,6 +111,18 @@ export function milestoneCheckPass(phaseId) {
121
111
  export function milestoneCheckFail(phaseId, reason) {
122
112
  return { phaseId, passed: false, reason };
123
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)";
124
126
  export function workflowNodeStatusFromTermination(termination) {
125
127
  if (termination === "completed")
126
128
  return "completed";
@@ -197,7 +199,7 @@ function nodeKindToKernel(n) {
197
199
  return { type: "tournament", entrants: n.tournament.entrants.map(workflowTaskToKernel) };
198
200
  return undefined;
199
201
  }
200
- /** 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
201
203
  * `submit_workflow_nodes` (R3-1) so the two encodings never drift. */
202
204
  export function workflowNodeSpecToKernel(n) {
203
205
  const kind = nodeKindToKernel(n);
@@ -223,28 +225,6 @@ export function workflowNodeSpecToKernel(n) {
223
225
  export function workflowSpecToKernel(spec) {
224
226
  return { nodes: spec.nodes.map(workflowNodeSpecToKernel) };
225
227
  }
226
- /** R3-1: map a batch of host nodes to the `submit_workflow_nodes` kernel event body. G1: pass
227
- * `submitterAgentId` so the kernel enforces no-privilege-escalation (quarantined submitter ⇒ its
228
- * nodes coerced to quarantined). Omitted ⇒ no coercion. */
229
- export function submitWorkflowNodesToKernel(nodes, submitterAgentId) {
230
- return {
231
- kind: "submit_workflow_nodes",
232
- nodes: nodes.map(workflowNodeSpecToKernel),
233
- ...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
234
- };
235
- }
236
- /** M5/G1: map an agent-authored spec to the `submit_workflow` kernel event body (the agent-reachable
237
- * `Syscall::LoadWorkflow`). The kernel bootstraps the DAG when none is active, else flattens onto it.
238
- * `parentSessionId` seeds child session ids on bootstrap; `submitterAgentId` carries G1 trust coercion
239
- * on the flatten case. */
240
- export function submitWorkflowToKernel(spec, parentSessionId, submitterAgentId) {
241
- return {
242
- kind: "submit_workflow",
243
- spec: workflowSpecToKernel(spec),
244
- parent_session_id: parentSessionId,
245
- ...(submitterAgentId ? { submitter_agent_id: submitterAgentId } : {}),
246
- };
247
- }
248
228
  /** R3-1: the tool a workflow-coordinator node's agent calls to append work to the running DAG. */
249
229
  /** Shared JSON-Schema for a workflow-node batch (a DAG). Used by both `submit_workflow_nodes`
250
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.49",
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.49"
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>;