@deepstrike/wasm 0.2.41 → 0.2.43

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.
@@ -7,6 +7,12 @@ export interface AttemptRequest {
7
7
  sessionId?: string;
8
8
  goal: string;
9
9
  criteria?: Criterion[];
10
+ /**
11
+ * Multimodal inputs (images / audio) attached to the task. Forwarded to every attempt
12
+ * unconditionally; the runner seeds them per session idempotently, so fresh-session carries
13
+ * re-seed while same-session carries do not double.
14
+ */
15
+ attachments?: import("../types.js").ContentPart[];
10
16
  extensions?: Record<string, unknown>;
11
17
  inheritEvents?: Array<{
12
18
  seq: number;
@@ -14,6 +14,7 @@ export class RuntimeAttemptBody {
14
14
  sessionId: context.sessionId,
15
15
  goal: context.goal,
16
16
  criteria: (context.criteria ?? []).map(criterion => criterion.text),
17
+ ...(context.attachments?.length ? { attachments: context.attachments } : {}),
17
18
  extensions: context.extensions,
18
19
  ...(context.attempt === 1 && context.inheritEvents
19
20
  ? { inheritEvents: context.inheritEvents }
@@ -160,6 +160,8 @@ export type KernelRunnerAction = {
160
160
  };
161
161
  export interface KernelObservation {
162
162
  kind: string;
163
+ operation?: string;
164
+ subject?: string;
163
165
  action?: string;
164
166
  rho_after?: number;
165
167
  sprint?: number;
@@ -22,6 +22,8 @@ export interface ResourceQuota {
22
22
  maxConcurrentSubagents?: number;
23
23
  /** Max sub-agent nesting depth (direct children of the root loop are depth 1). */
24
24
  maxSpawnDepth?: number;
25
+ /** Max nodes in one in-kernel workflow DAG, including dynamically submitted nodes. */
26
+ maxWorkflowNodes?: number;
25
27
  /** Rolling-window memory-write rate limit: at most `maxWrites` per any `windowMs` span. */
26
28
  memoryWritesPerWindow?: MemoryWriteRateLimit;
27
29
  }
@@ -41,6 +41,17 @@ function pendingCallIds(action) {
41
41
  default: return "effectId" in action ? [action.effectId] : [];
42
42
  }
43
43
  }
44
+ function controlRequestRejection(observations, operation) {
45
+ const rejected = observations.find(observation => observation.kind === "control_request_rejected"
46
+ && (!operation || observation.operation === operation));
47
+ if (!rejected)
48
+ return undefined;
49
+ return {
50
+ operation: rejected.operation ?? operation ?? "control_request",
51
+ ...(rejected.subject ? { subject: rejected.subject } : {}),
52
+ reason: typeof rejected.reason === "string" ? rejected.reason : "request denied",
53
+ };
54
+ }
44
55
  export class RuntimeRunner {
45
56
  opts;
46
57
  interrupted = false;
@@ -147,6 +158,12 @@ export class RuntimeRunner {
147
158
  async *run(req) {
148
159
  const prior = req.inheritEvents ?? await this.opts.sessionLog.read(req.sessionId);
149
160
  const midRun = isMidRun(prior);
161
+ // Idempotent per session: an earlier run's `run_started` already carries these attachments
162
+ // (same-session retry attempt), so replay reconstructs them — recording and seeding again
163
+ // would double them in history. Deduping at the append keeps live and replay in agreement.
164
+ const attachments = req.attachments?.length && !attachmentsAlreadySeeded(prior, req.attachments)
165
+ ? req.attachments
166
+ : undefined;
150
167
  if (!midRun) {
151
168
  await this.opts.sessionLog.append(req.sessionId, {
152
169
  kind: "run_started",
@@ -155,10 +172,10 @@ export class RuntimeRunner {
155
172
  criteria: req.criteria ?? [],
156
173
  agent_id: this.opts.agentId,
157
174
  system_prompt: this.opts.systemPrompt,
158
- ...(req.attachments?.length ? { attachments: req.attachments } : {}),
175
+ ...(attachments ? { attachments } : {}),
159
176
  });
160
177
  }
161
- yield* this.execute(req.sessionId, req.goal, req.criteria ?? [], req.extensions, prior.length > 0 ? prior : undefined, midRun, req.attachments);
178
+ yield* this.execute(req.sessionId, req.goal, req.criteria ?? [], req.extensions, prior.length > 0 ? prior : undefined, midRun, attachments);
162
179
  }
163
180
  async *wake(sessionId, extensions) {
164
181
  const events = await this.opts.sessionLog.read(sessionId);
@@ -869,7 +886,7 @@ export class RuntimeRunner {
869
886
  const spec = parseStartWorkflowSpec(call.arguments);
870
887
  if (spec) {
871
888
  this.pendingAuthoredWorkflows.push(spec);
872
- const out = "workflow authored; executing now";
889
+ const out = "workflow submitted for governance adjudication";
873
890
  toolResults.push({ callId: call.id, output: out, isError: false });
874
891
  yield { type: "tool_result", callId: call.id, content: out, isError: false };
875
892
  continue;
@@ -879,8 +896,9 @@ export class RuntimeRunner {
879
896
  ? parseStartWorkflowArgs(call.arguments)
880
897
  : parseSubmitWorkflowNodesArgs(call.arguments);
881
898
  yield { type: "workflow_nodes_submitted", nodes };
882
- toolResults.push({ callId: call.id, output: "submitted", isError: false });
883
- yield { type: "tool_result", callId: call.id, content: "submitted", isError: false };
899
+ const out = "workflow nodes submitted for parent governance adjudication";
900
+ toolResults.push({ callId: call.id, output: out, isError: false });
901
+ yield { type: "tool_result", callId: call.id, content: out, isError: false };
884
902
  }
885
903
  // O5 (PreToolUse-hook analog): stateful host veto over each kernel-approved call.
886
904
  // A blocked call never executes; its reason reaches the model as a denied result.
@@ -1189,8 +1207,25 @@ export class RuntimeRunner {
1189
1207
  });
1190
1208
  this.nextArchiveStart = await this.appendObservations(parentSessionId, runtime, this.nextArchiveStart);
1191
1209
  const spawned = findSpawnProcessObservation(observations);
1192
- if (!spawned)
1210
+ if (!spawned) {
1211
+ const rejected = controlRequestRejection(observations, "spawn_sub_agent");
1212
+ if (rejected) {
1213
+ return {
1214
+ agentId: rejected.subject ?? spec.identity.agentId,
1215
+ result: {
1216
+ termination: "error",
1217
+ finalMessage: {
1218
+ role: "assistant",
1219
+ content: `spawn_sub_agent denied: ${rejected.reason}`,
1220
+ toolCalls: [],
1221
+ },
1222
+ turnsUsed: 0,
1223
+ totalTokensUsed: 0,
1224
+ },
1225
+ };
1226
+ }
1193
1227
  throw new Error("spawn_sub_agent did not emit agent_process_changed");
1228
+ }
1194
1229
  const manifest = spawnObservationToManifest(spawned, spec, parentSessionId);
1195
1230
  const orchestrator = this.opts.subAgentOrchestrator ?? defaultSubAgentOrchestrator;
1196
1231
  const result = await orchestrator.run({
@@ -1392,6 +1427,7 @@ export class RuntimeRunner {
1392
1427
  config.resource_quota = {
1393
1428
  ...(q.maxConcurrentSubagents !== undefined ? { max_concurrent_subagents: q.maxConcurrentSubagents } : {}),
1394
1429
  ...(q.maxSpawnDepth !== undefined ? { max_spawn_depth: q.maxSpawnDepth } : {}),
1430
+ ...(q.maxWorkflowNodes !== undefined ? { max_workflow_nodes: q.maxWorkflowNodes } : {}),
1395
1431
  ...(q.memoryWritesPerWindow !== undefined
1396
1432
  ? { memory_writes_per_window: [q.memoryWritesPerWindow.maxWrites, q.memoryWritesPerWindow.windowMs] }
1397
1433
  : {}),
@@ -1641,6 +1677,11 @@ export class RuntimeRunner {
1641
1677
  }
1642
1678
  if (!initialAction)
1643
1679
  return { nodeOutcomes: [], outputs: {} };
1680
+ const workflowRejection = controlRequestRejection(observations);
1681
+ if (initialAction.kind === "call_provider" && workflowRejection) {
1682
+ this.workflowContinuation = initialAction;
1683
+ return { nodeOutcomes: [], outputs: {}, rejection: workflowRejection };
1684
+ }
1644
1685
  if (initialAction.kind !== "spawn_workflow") {
1645
1686
  throw new Error(`workflow load returned unexpected kernel effect: ${initialAction.kind}`);
1646
1687
  }
@@ -1687,6 +1728,22 @@ export class RuntimeRunner {
1687
1728
  const observationStart = this.pendingObservations.length;
1688
1729
  const submitAction = kernelMaybeAction(runtime, this.pendingObservations, submitEvent);
1689
1730
  const subObs = this.pendingObservations.slice(observationStart);
1731
+ const nodesRejected = subObs.find(observation => observation.kind === "nodes_rejected");
1732
+ const rejected = controlRequestRejection(subObs, "submit_workflow_nodes")
1733
+ ?? (nodesRejected
1734
+ ? { operation: "submit_workflow_nodes", reason: String(nodesRejected.reason ?? "request denied") }
1735
+ : undefined);
1736
+ if (rejected) {
1737
+ const denial = `workflow node submission denied: ${rejected.reason}`;
1738
+ result.result = {
1739
+ ...result.result,
1740
+ termination: "error",
1741
+ finalMessage: { role: "assistant", content: denial, toolCalls: [] },
1742
+ };
1743
+ outputs.set(result.agentId, denial);
1744
+ if (stableId !== result.agentId)
1745
+ outputs.set(stableId, denial);
1746
+ }
1690
1747
  if (submitAction?.kind === "spawn_workflow") {
1691
1748
  nextNodes.push(...submitAction.nodes);
1692
1749
  budget = submitAction.budget ?? budget;
@@ -2092,6 +2149,15 @@ function signalToKernelEvent(delivery) {
2092
2149
  },
2093
2150
  };
2094
2151
  }
2152
+ /**
2153
+ * True when an earlier run in this session already seeded the same attachments. Replay
2154
+ * reconstructs the attachment message from that run's `run_started`, so recording and
2155
+ * live-seeding them again (a same-session retry attempt) would double them in history.
2156
+ */
2157
+ function attachmentsAlreadySeeded(prior, attachments) {
2158
+ const wanted = JSON.stringify(attachments);
2159
+ return prior.some(({ event }) => event.kind === "run_started" && JSON.stringify(event.attachments ?? []) === wanted);
2160
+ }
2095
2161
  /** Convert SDK ContentParts (camelCase mediaType) to kernel serde shape (media_type). */
2096
2162
  function attachmentsToKernelMessage(parts) {
2097
2163
  const content = parts.map(p => {
@@ -196,9 +196,15 @@ export interface WorkflowNodeOutcome {
196
196
  termination?: TerminationReason;
197
197
  output?: Message;
198
198
  }
199
+ export interface ControlRequestRejection {
200
+ operation: string;
201
+ subject?: string;
202
+ reason: string;
203
+ }
199
204
  export interface WorkflowOutcome {
200
205
  nodeOutcomes: WorkflowNodeOutcome[];
201
206
  outputs: Record<string, string>;
207
+ rejection?: ControlRequestRejection;
202
208
  }
203
209
  export declare function workflowNodeOutcomeFromKernel(raw: KernelWorkflowNodeOutcome): WorkflowNodeOutcome;
204
210
  /** Per-node spawn descriptor carried in the `workflow_batch_spawned` observation. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.41",
3
+ "version": "0.2.43",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,12 +10,12 @@
10
10
  "README.md"
11
11
  ],
12
12
  "scripts": {
13
- "build:wasm": "wasm-pack build ../../crates/deepstrike-wasm --target bundler --out-dir ../../wasm/pkg",
13
+ "build:wasm": "wasm-pack build ../crates/deepstrike-wasm --target bundler --out-dir ../../wasm/pkg",
14
14
  "build": "tsc",
15
15
  "test": "node --experimental-vm-modules node_modules/.bin/jest"
16
16
  },
17
17
  "dependencies": {
18
- "@deepstrike/wasm-kernel": "0.2.41"
18
+ "@deepstrike/wasm-kernel": "0.2.43"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/jest": "^30.0.0",