@deepstrike/wasm 0.2.6 → 0.2.8

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.
package/dist/index.d.ts CHANGED
@@ -3,7 +3,8 @@ export type { NativeOsProfile, OsProfileId, MemoryPolicy, MemoryWriteRateLimit,
3
3
  export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
4
4
  export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
5
5
  export type { SubAgentRunContext } from "./runtime/sub-agent-orchestrator.js";
6
- export type { AgentCapabilityFilter, AgentIdentity, AgentIsolation, AgentRunSpec, AgentProcessChangedObservation, ContextInheritance, KernelAgentRole, LoopResult, MilestoneCheckResult, MilestoneContract, MilestonePhase, MilestonePolicy, SubAgentResult, TerminationReason, } from "./runtime/types/agent.js";
6
+ export type { AgentCapabilityFilter, AgentIdentity, AgentIsolation, AgentRunSpec, AgentProcessChangedObservation, ContextInheritance, KernelAgentRole, LoopResult, MilestoneCheckResult, MilestoneContract, MilestonePhase, MilestonePolicy, SubAgentResult, TerminationReason, WorkflowSpec, WorkflowNodeSpec, WorkflowTaskSpec, WorkflowSpawnInfo, } from "./runtime/types/agent.js";
7
+ export { workflowSpecToKernel, fanoutSynthesize, generateAndFilter, verifyRules } from "./runtime/types/agent.js";
7
8
  export { Governance } from "./governance.js";
8
9
  export type { GovernanceVerdict } from "./governance.js";
9
10
  export { AnthropicProvider } from "./providers/anthropic.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { RuntimeRunner, collectText, InMemorySessionLog, LocalExecutionPlane, DEFAULT_NATIVE_ATTENTION_POLICY, DEFAULT_NATIVE_GOVERNANCE_POLICY, DEFAULT_SANDBOX_POLICY, assertNativeProfile, osProfile, validateDeclarativePolicy, } from "./runtime/index.js";
2
2
  export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
3
3
  export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
4
+ export { workflowSpecToKernel, fanoutSynthesize, generateAndFilter, verifyRules } from "./runtime/types/agent.js";
4
5
  export { Governance } from "./governance.js";
5
6
  export { AnthropicProvider } from "./providers/anthropic.js";
6
7
  export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider, KimiProvider } from "./providers/openai.js";
@@ -1,10 +1,11 @@
1
- import type { RenderedContext, ToolSchema, StreamEvent, LLMProvider, Message, ProviderReplay } from "../types.js";
1
+ import type { RenderedContext, ToolSchema, StreamEvent, LLMProvider, Message, ProviderDescriptor, ProviderReplay } from "../types.js";
2
2
  export declare class AnthropicProvider implements LLMProvider {
3
3
  private readonly apiKey;
4
4
  private readonly model;
5
5
  private readonly maxTokens;
6
6
  private nativeAssistantBlocks;
7
7
  constructor(apiKey: string, model?: string, maxTokens?: number);
8
+ descriptor(): ProviderDescriptor;
8
9
  peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
9
10
  seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
10
11
  complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
@@ -17,6 +17,22 @@ export class AnthropicProvider {
17
17
  this.model = model;
18
18
  this.maxTokens = maxTokens;
19
19
  }
20
+ descriptor() {
21
+ return {
22
+ provider: "anthropic",
23
+ protocol: "anthropic-messages",
24
+ model: this.model,
25
+ reasoning: {
26
+ supported: true,
27
+ preserveAcrossToolTurns: true,
28
+ requiresReplayForToolTurns: true,
29
+ },
30
+ toolCalls: {
31
+ supported: true,
32
+ requiresStrictPairing: true,
33
+ },
34
+ };
35
+ }
20
36
  peekProviderReplay(message) {
21
37
  const blocks = this.nativeAssistantBlocks.get(assistantReplayKey(message));
22
38
  return blocks?.length ? { native_blocks: blocks } : undefined;
@@ -24,7 +40,13 @@ export class AnthropicProvider {
24
40
  seedProviderReplay(message, replay) {
25
41
  if (replay.native_blocks?.length) {
26
42
  this.nativeAssistantBlocks.set(assistantReplayKey(message), replay.native_blocks);
43
+ return;
27
44
  }
45
+ // Legacy log without persisted native blocks: reconstruct neutral
46
+ // text + tool_use blocks so a tool-use turn can be replayed.
47
+ const blocks = reconstructAnthropicBlocks(message);
48
+ if (blocks.length)
49
+ this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
28
50
  }
29
51
  async complete(context, tools, extensions) {
30
52
  return collectStreamMessage(this.stream(context, tools, extensions));
@@ -154,3 +176,26 @@ export class AnthropicProvider {
154
176
  this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
155
177
  }
156
178
  }
179
+ /**
180
+ * Reconstruct Anthropic assistant content blocks from a neutral transcript when
181
+ * no provider replay was persisted. Only meaningful for tool-use turns.
182
+ */
183
+ function reconstructAnthropicBlocks(message) {
184
+ const toolCalls = message.toolCalls ?? [];
185
+ if (!toolCalls.length)
186
+ return [];
187
+ const blocks = [];
188
+ if (message.content)
189
+ blocks.push({ type: "text", text: message.content });
190
+ for (const tc of toolCalls) {
191
+ let input = {};
192
+ try {
193
+ input = JSON.parse(tc.arguments || "{}");
194
+ }
195
+ catch {
196
+ input = {};
197
+ }
198
+ blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input });
199
+ }
200
+ return blocks;
201
+ }
@@ -198,6 +198,26 @@ export function kernelObservationToSessionEvent(obs, turn, opts = {}) {
198
198
  memory_id: obs.memory_id ?? "",
199
199
  error: obs.error ?? "",
200
200
  });
201
+ case "workflow_batch_spawned": {
202
+ const nodes = obs.nodes ?? [];
203
+ return withCategory({
204
+ kind: "workflow_batch_spawned",
205
+ turn: t,
206
+ node_count: nodes.length,
207
+ node_ids: nodes.map((n) => n.agent_id ?? ""),
208
+ });
209
+ }
210
+ case "workflow_completed": {
211
+ const completed = obs.completed ?? [];
212
+ const failed = obs.failed ?? [];
213
+ return withCategory({
214
+ kind: "workflow_completed",
215
+ turn: t,
216
+ completed,
217
+ failed,
218
+ total_nodes: completed.length + failed.length,
219
+ });
220
+ }
201
221
  default:
202
222
  return null;
203
223
  }
@@ -93,6 +93,16 @@ export interface KernelObservation {
93
93
  requires_async_response?: boolean;
94
94
  /** memory_validation_failed (Phase 7). */
95
95
  error?: string;
96
+ nodes?: Array<{
97
+ agent_id: string;
98
+ goal: string;
99
+ role: string;
100
+ isolation: string;
101
+ context_inheritance: string;
102
+ model_hint?: string;
103
+ }>;
104
+ completed?: string[];
105
+ failed?: string[];
96
106
  }
97
107
  export declare function toolSchemaToKernel(schema: ToolSchema): Record<string, unknown>;
98
108
  export declare function skillMetadataToKernel(skill: SkillMetadata): Record<string, unknown>;
@@ -1,6 +1,12 @@
1
- import type { LLMProvider, Message, ProviderReplay, ToolCall } from "../types.js";
1
+ import type { LLMProvider, Message, ProviderDescriptor, ProviderReplay, ToolCall } from "../types.js";
2
2
  import type { SessionEvent } from "./session-log.js";
3
3
  export declare function assistantReplayKey(message: Pick<Message, "content" | "toolCalls">): string;
4
+ /**
5
+ * A stored replay may only be seeded into a provider speaking the same wire
6
+ * protocol; on a cross-protocol fallback the incompatible envelope is skipped so
7
+ * the new provider re-serializes neutral context instead.
8
+ */
9
+ export declare function isReplayCompatibleWithProvider(replay: ProviderReplay, descriptor: ProviderDescriptor | undefined): boolean;
4
10
  export declare function seedProviderReplayFromEvents(provider: LLMProvider, events: Array<{
5
11
  event: SessionEvent;
6
12
  }>): void;
@@ -1,21 +1,46 @@
1
- import { effectiveProviderReplay } from "./session-repair.js";
2
1
  export function assistantReplayKey(message) {
3
2
  return JSON.stringify({
4
3
  content: message.content,
5
4
  toolCalls: message.toolCalls ?? [],
6
5
  });
7
6
  }
7
+ /** Infer the wire protocol a stored replay envelope belongs to (explicit, or by shape for legacy logs). */
8
+ function replayProtocol(replay) {
9
+ if (replay.protocol)
10
+ return replay.protocol;
11
+ if (replay.native_blocks?.length)
12
+ return "anthropic-messages";
13
+ if (replay.reasoning_content != null || replay.reasoning_details !== undefined)
14
+ return "openai-chat";
15
+ return undefined;
16
+ }
17
+ /**
18
+ * A stored replay may only be seeded into a provider speaking the same wire
19
+ * protocol; on a cross-protocol fallback the incompatible envelope is skipped so
20
+ * the new provider re-serializes neutral context instead.
21
+ */
22
+ export function isReplayCompatibleWithProvider(replay, descriptor) {
23
+ if (!descriptor)
24
+ return true;
25
+ const protocol = replayProtocol(replay);
26
+ if (!protocol)
27
+ return true;
28
+ return protocol === descriptor.protocol;
29
+ }
8
30
  export function seedProviderReplayFromEvents(provider, events) {
9
31
  if (!provider.seedProviderReplay)
10
32
  return;
33
+ const descriptor = provider.descriptor?.();
11
34
  for (const { event } of events) {
12
35
  if (event.kind !== "llm_completed")
13
36
  continue;
14
37
  const toolCalls = event.tool_calls ?? [];
15
- const replay = effectiveProviderReplay(event.content, toolCalls, event.provider_replay);
16
- if (!replay)
38
+ const stored = event.provider_replay;
39
+ if (stored && !isReplayCompatibleWithProvider(stored, descriptor))
17
40
  continue;
18
- provider.seedProviderReplay({ content: event.content, toolCalls }, replay);
41
+ // Pass the message even with no persisted replay: a provider may reconstruct
42
+ // a legacy replay (e.g. Anthropic native_blocks) from the neutral transcript.
43
+ provider.seedProviderReplay({ content: event.content, toolCalls }, stored ?? {});
19
44
  }
20
45
  }
21
46
  export function peekProviderReplay(provider, content, toolCalls) {
@@ -6,7 +6,7 @@ import type { SignalSource } from "../signals/index.js";
6
6
  import type { SessionLog, SessionEvent } from "./session-log.js";
7
7
  import type { ExecutionPlane } from "./execution-plane.js";
8
8
  import { type GovernancePolicy } from "../governance.js";
9
- import type { AgentRunSpec, SubAgentResult, MilestonePolicy, MilestoneContract, MilestoneCheckResult } from "./types/agent.js";
9
+ import type { AgentRunSpec, SubAgentResult, MilestonePolicy, MilestoneContract, MilestoneCheckResult, WorkflowSpec } from "./types/agent.js";
10
10
  import { type SubAgentOrchestrator } from "./sub-agent-orchestrator.js";
11
11
  import { type NativeOsProfile, type OsProfileId } from "./os-profile.js";
12
12
  import { LargeResultSpool } from "./large-result-spool.js";
@@ -113,6 +113,26 @@ export declare class RuntimeRunner {
113
113
  dream(agentId: string, nowMs?: number): Promise<DreamResult>;
114
114
  private execute;
115
115
  spawnSubAgent(spec: AgentRunSpec): Promise<SubAgentResult>;
116
+ /**
117
+ * W0-ABI: run a declarative workflow DAG. The kernel owns the DAG and gates every node spawn
118
+ * through the syscall trap; this driver runs each kernel-emitted batch of nodes in parallel,
119
+ * feeds their results back, and loops until the kernel reports the workflow complete.
120
+ */
121
+ runWorkflow(spec: WorkflowSpec, opts?: {
122
+ resumedCompleted?: string[];
123
+ }): Promise<{
124
+ completed: string[];
125
+ failed: string[];
126
+ }>;
127
+ /**
128
+ * Resume a workflow from the parent session's completed nodes.
129
+ * Reads the session log, extracts completed workflow node agent_ids, and
130
+ * calls runWorkflow with resumedCompleted so the kernel skips those nodes.
131
+ */
132
+ resumeWorkflow(spec: WorkflowSpec): Promise<{
133
+ completed: string[];
134
+ failed: string[];
135
+ }>;
116
136
  private appendObservations;
117
137
  private archiveSemanticPageOut;
118
138
  }
@@ -3,9 +3,9 @@ import { governancePolicyToKernelEvent } from "../governance.js";
3
3
  import { getKernel } from "./kernel.js";
4
4
  import { peekProviderReplay, seedProviderReplayFromEvents } from "./provider-replay.js";
5
5
  import { sanitizeReplayText } from "./replay-sanitize.js";
6
- import { buildLlmCompletedEvent, buildRunTerminalEvent, repairEventsForRecovery } from "./session-repair.js";
6
+ import { buildLlmCompletedEvent, buildRunTerminalEvent, buildWorkflowNodeCompletedEvent, recoverCompletedWorkflowNodes, repairEventsForRecovery, } from "./session-repair.js";
7
7
  import { forceCompact, kernelAction, kernelApply, kernelMaybeAction, messageToKernelMessage, skillMetadataToKernel, toolResultToKernel, toolSchemaToKernel, } from "./kernel-step.js";
8
- import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, } from "./types/agent.js";
8
+ import { agentRunSpecToKernel, findSpawnProcessObservation, milestoneCheckPass, milestoneCheckResultToKernel, spawnObservationToManifest, subAgentResultToKernel, workflowNodeToManifest, workflowNodeToSpec, workflowSpecToKernel, } from "./types/agent.js";
9
9
  import { defaultSubAgentOrchestrator } from "./sub-agent-orchestrator.js";
10
10
  import { kernelObservationToSessionEvent, withCategory } from "./kernel-event-log.js";
11
11
  import { assertNativeProfile } from "./os-profile.js";
@@ -706,6 +706,68 @@ export class RuntimeRunner {
706
706
  });
707
707
  return result;
708
708
  }
709
+ /**
710
+ * W0-ABI: run a declarative workflow DAG. The kernel owns the DAG and gates every node spawn
711
+ * through the syscall trap; this driver runs each kernel-emitted batch of nodes in parallel,
712
+ * feeds their results back, and loops until the kernel reports the workflow complete.
713
+ */
714
+ async runWorkflow(spec, opts) {
715
+ if (!this.activeKernel || !this.currentSessionId) {
716
+ throw new Error("runWorkflow requires an active parent run");
717
+ }
718
+ const parentSessionId = this.currentSessionId;
719
+ const runtime = this.activeKernel;
720
+ const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
721
+ let observations = kernelApply(runtime, this.pendingObservations, {
722
+ kind: "load_workflow",
723
+ spec: workflowSpecToKernel(spec),
724
+ parent_session_id: parentSessionId,
725
+ // W0-ABI resume: skip nodes already completed before an interruption.
726
+ ...(opts?.resumedCompleted && opts.resumedCompleted.length ? { resumed_completed: opts.resumedCompleted } : {}),
727
+ });
728
+ for (;;) {
729
+ const done = observations.find(o => o.kind === "workflow_completed");
730
+ if (done)
731
+ return { completed: done.completed ?? [], failed: done.failed ?? [] };
732
+ const batch = observations.find(o => o.kind === "workflow_batch_spawned");
733
+ const nodes = batch?.nodes ?? [];
734
+ if (nodes.length === 0)
735
+ return { completed: [], failed: [] };
736
+ const results = await Promise.all(nodes.map(node => orchestrator.run({
737
+ parentOpts: this.opts,
738
+ parentSessionId,
739
+ spec: workflowNodeToSpec(node, parentSessionId),
740
+ manifest: workflowNodeToManifest(node, parentSessionId),
741
+ sessionLog: this.opts.sessionLog,
742
+ })));
743
+ observations = [];
744
+ for (const result of results) {
745
+ observations = kernelApply(runtime, this.pendingObservations, {
746
+ kind: "sub_agent_completed",
747
+ result: subAgentResultToKernel(result),
748
+ });
749
+ // Persist node completion for resume recovery.
750
+ await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodeCompletedEvent({
751
+ turn: runtime.turn(),
752
+ agentId: result.agentId,
753
+ termination: result.result.termination,
754
+ }));
755
+ }
756
+ }
757
+ }
758
+ /**
759
+ * Resume a workflow from the parent session's completed nodes.
760
+ * Reads the session log, extracts completed workflow node agent_ids, and
761
+ * calls runWorkflow with resumedCompleted so the kernel skips those nodes.
762
+ */
763
+ async resumeWorkflow(spec) {
764
+ if (!this.currentSessionId) {
765
+ throw new Error("resumeWorkflow requires an active parent run");
766
+ }
767
+ const events = await this.opts.sessionLog.read(this.currentSessionId);
768
+ const resumedCompleted = recoverCompletedWorkflowNodes(events);
769
+ return this.runWorkflow(spec, { resumedCompleted });
770
+ }
709
771
  async appendObservations(sessionId, runtime, nextArchiveStart) {
710
772
  const turn = runtime.turn();
711
773
  const preservedRefs = runtime.preservedRefs();
@@ -232,6 +232,28 @@ export type SessionEvent = {
232
232
  primitive?: KernelPrimitive;
233
233
  memory_id: string;
234
234
  error: string;
235
+ } | {
236
+ kind: "workflow_node_completed";
237
+ turn: number;
238
+ category?: KernelEventCategory;
239
+ primitive?: KernelPrimitive;
240
+ agent_id: string;
241
+ termination: string;
242
+ } | {
243
+ kind: "workflow_batch_spawned";
244
+ turn: number;
245
+ category?: KernelEventCategory;
246
+ primitive?: KernelPrimitive;
247
+ node_count: number;
248
+ node_ids: string[];
249
+ } | {
250
+ kind: "workflow_completed";
251
+ turn: number;
252
+ category?: KernelEventCategory;
253
+ primitive?: KernelPrimitive;
254
+ completed: string[];
255
+ failed: string[];
256
+ total_nodes: number;
235
257
  } | {
236
258
  kind: "run_terminal";
237
259
  reason: string;
@@ -1,8 +1,13 @@
1
1
  import type { ProviderReplay, ToolCall } from "../types.js";
2
2
  import type { SessionEvent } from "./session-log.js";
3
3
  export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay-sanitize.js";
4
- export declare function synthesizeProviderReplay(content: string, toolCalls: ToolCall[]): ProviderReplay | undefined;
5
- export declare function effectiveProviderReplay(content: string, toolCalls: ToolCall[], stored?: ProviderReplay): ProviderReplay | undefined;
4
+ /**
5
+ * Normalize a persisted llm_completed event for recovery. Content is sanitized
6
+ * and token_count backfilled, but the stored `provider_replay` envelope is
7
+ * passed through verbatim — this layer is provider-neutral and never
8
+ * synthesizes protocol-specific replay shapes. Legacy reconstruction is the
9
+ * responsibility of the target provider's `seedProviderReplay`.
10
+ */
6
11
  export declare function normalizeLlmCompleted(event: Extract<SessionEvent, {
7
12
  kind: "llm_completed";
8
13
  }>, maxBytes?: number): Extract<SessionEvent, {
@@ -31,3 +36,19 @@ export declare function buildRunTerminalEvent(input: {
31
36
  }): Extract<SessionEvent, {
32
37
  kind: "run_terminal";
33
38
  }>;
39
+ export declare function buildWorkflowNodeCompletedEvent(input: {
40
+ turn: number;
41
+ agentId: string;
42
+ termination: string;
43
+ }): Extract<SessionEvent, {
44
+ kind: "workflow_node_completed";
45
+ }>;
46
+ /**
47
+ * Recover completed workflow node agent_ids from a session event stream.
48
+ * Scans for workflow_node_completed events and returns the agent_ids whose
49
+ * termination was "completed". Used to rebuild resumedCompleted for resumeWorkflow.
50
+ */
51
+ export declare function recoverCompletedWorkflowNodes(events: Array<{
52
+ seq: number;
53
+ event: SessionEvent;
54
+ }>): string[];
@@ -3,40 +3,17 @@ export { REPLAY_CONTENT_MAX_BYTES as RECOVERY_CONTENT_MAX_BYTES } from "./replay
3
3
  function estimateTokenCount(text) {
4
4
  return Math.max(1, Math.ceil(text.length / 4));
5
5
  }
6
- function parseToolInput(args) {
7
- try {
8
- return JSON.parse(args || "{}");
9
- }
10
- catch {
11
- return {};
12
- }
13
- }
14
- export function synthesizeProviderReplay(content, toolCalls) {
15
- if (!toolCalls.length)
16
- return undefined;
17
- const blocks = [];
18
- if (content)
19
- blocks.push({ type: "text", text: content });
20
- for (const tc of toolCalls) {
21
- blocks.push({
22
- type: "tool_use",
23
- id: tc.id,
24
- name: tc.name,
25
- input: parseToolInput(tc.arguments),
26
- });
27
- }
28
- return { native_blocks: blocks };
29
- }
30
- export function effectiveProviderReplay(content, toolCalls, stored) {
31
- if (stored?.native_blocks?.length || stored?.reasoning_content != null) {
32
- return stored;
33
- }
34
- return synthesizeProviderReplay(content, toolCalls);
35
- }
6
+ /**
7
+ * Normalize a persisted llm_completed event for recovery. Content is sanitized
8
+ * and token_count backfilled, but the stored `provider_replay` envelope is
9
+ * passed through verbatim — this layer is provider-neutral and never
10
+ * synthesizes protocol-specific replay shapes. Legacy reconstruction is the
11
+ * responsibility of the target provider's `seedProviderReplay`.
12
+ */
36
13
  export function normalizeLlmCompleted(event, maxBytes) {
37
14
  const content = sanitizeReplayText(event.content ?? "", maxBytes);
38
15
  const toolCalls = event.tool_calls ?? [];
39
- const providerReplay = effectiveProviderReplay(content, toolCalls, event.provider_replay);
16
+ const providerReplay = event.provider_replay;
40
17
  return {
41
18
  kind: "llm_completed",
42
19
  turn: event.turn,
@@ -71,3 +48,25 @@ export function buildRunTerminalEvent(input) {
71
48
  total_tokens: Math.max(0, input.totalTokens),
72
49
  };
73
50
  }
51
+ export function buildWorkflowNodeCompletedEvent(input) {
52
+ return {
53
+ kind: "workflow_node_completed",
54
+ turn: input.turn,
55
+ agent_id: input.agentId,
56
+ termination: input.termination,
57
+ };
58
+ }
59
+ /**
60
+ * Recover completed workflow node agent_ids from a session event stream.
61
+ * Scans for workflow_node_completed events and returns the agent_ids whose
62
+ * termination was "completed". Used to rebuild resumedCompleted for resumeWorkflow.
63
+ */
64
+ export function recoverCompletedWorkflowNodes(events) {
65
+ const completed = [];
66
+ for (const { event } of events) {
67
+ if (event.kind === "workflow_node_completed" && event.termination === "completed") {
68
+ completed.push(event.agent_id);
69
+ }
70
+ }
71
+ return completed;
72
+ }
@@ -75,3 +75,47 @@ export declare function milestoneCheckResultToKernel(result: MilestoneCheckResul
75
75
  export declare function subAgentResultToKernel(result: SubAgentResult): Record<string, unknown>;
76
76
  export declare function milestoneCheckPass(phaseId: string): MilestoneCheckResult;
77
77
  export declare function milestoneCheckFail(phaseId: string, reason: string): MilestoneCheckResult;
78
+ /** A task for a workflow node: a full object, or a bare goal string. */
79
+ export type WorkflowTaskSpec = {
80
+ goal: string;
81
+ criteria?: string[];
82
+ lane?: string;
83
+ } | string;
84
+ /** One node in a declarative workflow DAG (camelCase host shape). */
85
+ export interface WorkflowNodeSpec {
86
+ task: WorkflowTaskSpec;
87
+ role: KernelAgentRole;
88
+ isolation?: AgentIsolation;
89
+ contextInheritance?: ContextInheritance;
90
+ modelHint?: string;
91
+ /** Indices of nodes this node depends on. */
92
+ dependsOn?: number[];
93
+ }
94
+ /** A declarative workflow DAG the kernel runs node-by-node, gating each spawn. */
95
+ export interface WorkflowSpec {
96
+ nodes: WorkflowNodeSpec[];
97
+ }
98
+ /** Per-node spawn descriptor carried in the `workflow_batch_spawned` observation. */
99
+ export interface WorkflowSpawnInfo {
100
+ agent_id: string;
101
+ goal: string;
102
+ role: string;
103
+ isolation: string;
104
+ context_inheritance: string;
105
+ model_hint?: string;
106
+ }
107
+ /** Map a host `WorkflowSpec` to the snake_case kernel JSON (`load_workflow.spec`). */
108
+ export declare function workflowSpecToKernel(spec: WorkflowSpec): Record<string, unknown>;
109
+ /** Build a sub-agent run spec for a kernel-generated workflow node. */
110
+ export declare function workflowNodeToSpec(node: WorkflowSpawnInfo, parentSessionId: string): AgentRunSpec;
111
+ /** Build the host manifest for a kernel-generated workflow node. */
112
+ export declare function workflowNodeToManifest(node: WorkflowSpawnInfo, parentSessionId: string): AgentProcessChangedObservation;
113
+ /** N parallel read-only Explore workers feeding a single Plan synthesizer (barrier). */
114
+ export declare function fanoutSynthesize(workers: WorkflowTaskSpec[], synthesize: WorkflowTaskSpec): WorkflowSpec;
115
+ /** N parallel Implement generators feeding a single Verify filter/dedupe step (barrier). */
116
+ export declare function generateAndFilter(generators: WorkflowTaskSpec[], filter: WorkflowTaskSpec): WorkflowSpec;
117
+ /**
118
+ * One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
119
+ * re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
120
+ */
121
+ export declare function verifyRules(rules: WorkflowTaskSpec[], skeptic?: WorkflowTaskSpec): WorkflowSpec;
@@ -94,3 +94,111 @@ export function milestoneCheckPass(phaseId) {
94
94
  export function milestoneCheckFail(phaseId, reason) {
95
95
  return { phaseId, passed: false, reason };
96
96
  }
97
+ /** Map a host `WorkflowSpec` to the snake_case kernel JSON (`load_workflow.spec`). */
98
+ export function workflowSpecToKernel(spec) {
99
+ return {
100
+ nodes: spec.nodes.map(n => {
101
+ const task = typeof n.task === "string" ? { goal: n.task } : n.task;
102
+ return {
103
+ task: {
104
+ goal: task.goal,
105
+ // `criteria` is required by the kernel's RuntimeTask serde (no default).
106
+ criteria: task.criteria ?? [],
107
+ ...(task.lane ? { lane: task.lane } : {}),
108
+ },
109
+ role: n.role,
110
+ isolation: n.isolation ?? "shared",
111
+ context_inheritance: n.contextInheritance ?? "none",
112
+ ...(n.modelHint ? { model_hint: n.modelHint } : {}),
113
+ ...(n.dependsOn && n.dependsOn.length ? { depends_on: n.dependsOn } : {}),
114
+ };
115
+ }),
116
+ };
117
+ }
118
+ /** Build a sub-agent run spec for a kernel-generated workflow node. */
119
+ export function workflowNodeToSpec(node, parentSessionId) {
120
+ return {
121
+ identity: {
122
+ agentId: node.agent_id,
123
+ sessionId: `${parentSessionId}-${node.agent_id}`,
124
+ isSubAgent: true,
125
+ parentSessionId,
126
+ },
127
+ role: node.role,
128
+ isolation: node.isolation,
129
+ goal: node.goal,
130
+ };
131
+ }
132
+ /** Build the host manifest for a kernel-generated workflow node. */
133
+ export function workflowNodeToManifest(node, parentSessionId) {
134
+ return {
135
+ kind: "agent_process_changed",
136
+ agent_id: node.agent_id,
137
+ parent_session_id: parentSessionId,
138
+ role: node.role,
139
+ isolation: node.isolation,
140
+ context_inheritance: node.context_inheritance,
141
+ };
142
+ }
143
+ // ─── W1/W2 workflow templates (the six patterns as one-liners) ───
144
+ // Roles carry the kernel's role_defaults isolation/inheritance so host-built specs match the
145
+ // core `orchestration::workflow` constructors (e.g. verifiers stay bias-resistant).
146
+ function asTask(t) {
147
+ return typeof t === "string" ? { goal: t } : t;
148
+ }
149
+ /** N parallel read-only Explore workers feeding a single Plan synthesizer (barrier). */
150
+ export function fanoutSynthesize(workers, synthesize) {
151
+ const nodes = workers.map(t => ({
152
+ task: asTask(t),
153
+ role: "explore",
154
+ isolation: "read_only",
155
+ contextInheritance: "system_only",
156
+ }));
157
+ nodes.push({
158
+ task: asTask(synthesize),
159
+ role: "plan",
160
+ isolation: "shared",
161
+ contextInheritance: "full",
162
+ dependsOn: workers.map((_, i) => i),
163
+ });
164
+ return { nodes };
165
+ }
166
+ /** N parallel Implement generators feeding a single Verify filter/dedupe step (barrier). */
167
+ export function generateAndFilter(generators, filter) {
168
+ const nodes = generators.map(t => ({
169
+ task: asTask(t),
170
+ role: "implement",
171
+ isolation: "worktree",
172
+ contextInheritance: "full",
173
+ }));
174
+ nodes.push({
175
+ task: asTask(filter),
176
+ role: "verify",
177
+ isolation: "read_only",
178
+ contextInheritance: "none",
179
+ dependsOn: generators.map((_, i) => i),
180
+ });
181
+ return { nodes };
182
+ }
183
+ /**
184
+ * One fresh-context verifier per rule/claim (parallel) + optional skeptic that depends on all and
185
+ * re-checks flags. Verifiers run read-only with no inherited author context (bias-resistant).
186
+ */
187
+ export function verifyRules(rules, skeptic) {
188
+ const nodes = rules.map(t => ({
189
+ task: asTask(t),
190
+ role: "verify",
191
+ isolation: "read_only",
192
+ contextInheritance: "none",
193
+ }));
194
+ if (skeptic !== undefined) {
195
+ nodes.push({
196
+ task: asTask(skeptic),
197
+ role: "verify",
198
+ isolation: "read_only",
199
+ contextInheritance: "none",
200
+ dependsOn: rules.map((_, i) => i),
201
+ });
202
+ }
203
+ return { nodes };
204
+ }
package/dist/types.d.ts CHANGED
@@ -112,9 +112,31 @@ export interface ToolArgumentRepairedEvent extends StreamEvent {
112
112
  * The framework creates and threads this object; providers may read/write it.
113
113
  */
114
114
  export type ProviderRunState = Record<string, unknown>;
115
+ export type ProviderProtocol = "anthropic-messages" | "openai-chat" | "openai-responses" | "gemini";
116
+ export interface ProviderDescriptor {
117
+ provider: string;
118
+ protocol: ProviderProtocol;
119
+ model: string;
120
+ reasoning: {
121
+ supported: boolean;
122
+ preserveAcrossToolTurns: boolean;
123
+ requiresReplayForToolTurns?: boolean;
124
+ };
125
+ toolCalls: {
126
+ supported: boolean;
127
+ requiresStrictPairing: boolean;
128
+ };
129
+ }
115
130
  export interface ProviderReplay {
131
+ schema_version?: 1 | 2;
132
+ provider?: string;
133
+ protocol?: ProviderProtocol;
134
+ model?: string;
116
135
  native_blocks?: Array<Record<string, unknown>>;
117
136
  reasoning_content?: string;
137
+ reasoning_details?: unknown;
138
+ native_message?: unknown;
139
+ tool_calls?: unknown[];
118
140
  }
119
141
  export interface LLMProvider {
120
142
  createRunState?(): ProviderRunState;
@@ -122,6 +144,7 @@ export interface LLMProvider {
122
144
  maxTurns?: number;
123
145
  timeoutMs?: number;
124
146
  };
147
+ descriptor?(): ProviderDescriptor;
125
148
  peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
126
149
  seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
127
150
  complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "test": "node --experimental-vm-modules node_modules/.bin/jest"
16
16
  },
17
17
  "dependencies": {
18
- "@deepstrike/wasm-kernel": "0.2.6"
18
+ "@deepstrike/wasm-kernel": "0.2.8"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",