@deepstrike/wasm 0.2.80 → 0.2.82

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.
@@ -1,6 +1,8 @@
1
1
  import { requestSnapshot } from "./prepared-request.js";
2
2
  import { collectStreamMessage, toOpenAIMessages } from "./base.js";
3
3
  import { malformedToolArguments } from "../runtime/tool-arguments.js";
4
+ import { ProviderError } from "./provider-error.js";
5
+ import { TEXTUAL_TOOL_CALL_CODE, TEXTUAL_TOOL_CALL_MESSAGE, markupStart, parseTextToolCalls, partialMarkupLength, textToolCallId } from "./text-tool-calls.js";
4
6
  const DEEPSEEK_REASONERS = new Set(["deepseek-reasoner", "deepseek-r1"]);
5
7
  const MINIMAX_REASONERS = new Set(["MiniMax-M1", "minimax-m1"]);
6
8
  // OpenAI-compatible provider — works for OpenAI, Qwen (DashScope), DeepSeek, MiniMax, Kimi
@@ -61,7 +63,31 @@ export class OpenAIProvider {
61
63
  // Phase 4: OpenAI signals an output-cap truncation via finish_reason="length"; the kernel
62
64
  // treats it as a truncation (== Anthropic "max_tokens") and drives output-cap recovery.
63
65
  let finishReason;
64
- while (true) {
66
+ // Plan task 9.0 · DeepSeek may write its tool calls as DSML markup in the text: hold it out of
67
+ // the text and parse it into tool calls at the end of the turn.
68
+ const watchMarkup = this.dialect === "deepseek";
69
+ let held = "";
70
+ let markup;
71
+ const visible = (text) => {
72
+ if (!watchMarkup)
73
+ return text;
74
+ if (markup !== undefined) {
75
+ markup += text;
76
+ return "";
77
+ }
78
+ const combined = held + text;
79
+ held = "";
80
+ const at = markupStart(combined);
81
+ if (at >= 0) {
82
+ markup = combined.slice(at);
83
+ return combined.slice(0, at);
84
+ }
85
+ const keep = partialMarkupLength(combined);
86
+ held = combined.slice(combined.length - keep);
87
+ return combined.slice(0, combined.length - keep);
88
+ };
89
+ let streamDone = false;
90
+ while (!streamDone) {
65
91
  const { done, value } = await reader.read();
66
92
  if (done)
67
93
  break;
@@ -73,11 +99,9 @@ export class OpenAIProvider {
73
99
  continue;
74
100
  const data = line.slice(6).trim();
75
101
  if (data === "[DONE]") {
76
- // Surface an output-cap truncation (finish_reason="length") to the runner/kernel. Emitted
77
- // as a usage frame (the channel the runner reads stopReason from) before the early return.
78
- if (finishReason)
79
- yield { type: "usage", totalTokens: 0, stopReason: finishReason };
80
- return;
102
+ // The tool calls accumulated so far are emitted below: returning here dropped them.
103
+ streamDone = true;
104
+ break;
81
105
  }
82
106
  try {
83
107
  const chunk = JSON.parse(data);
@@ -88,8 +112,11 @@ export class OpenAIProvider {
88
112
  continue;
89
113
  if (exposeReasoning && typeof delta.reasoning_content === "string" && delta.reasoning_content)
90
114
  yield { type: "thinking_delta", delta: delta.reasoning_content };
91
- if (typeof delta.content === "string" && delta.content)
92
- yield { type: "text_delta", delta: delta.content };
115
+ if (typeof delta.content === "string" && delta.content) {
116
+ const text = visible(delta.content);
117
+ if (text)
118
+ yield { type: "text_delta", delta: text };
119
+ }
93
120
  for (const tc of delta.tool_calls ?? []) {
94
121
  const idx = tc.index;
95
122
  if (!toolAccum[idx])
@@ -100,6 +127,25 @@ export class OpenAIProvider {
100
127
  catch { /* skip */ }
101
128
  }
102
129
  }
130
+ if (held)
131
+ yield { type: "text_delta", delta: held };
132
+ if (markup !== undefined) {
133
+ const calls = parseTextToolCalls(markup);
134
+ if (!calls) {
135
+ throw new ProviderError({
136
+ provider: this.providerId(),
137
+ kind: "protocol",
138
+ retryable: true,
139
+ message: TEXTUAL_TOOL_CALL_MESSAGE,
140
+ providerCode: TEXTUAL_TOOL_CALL_CODE,
141
+ });
142
+ }
143
+ let index = Math.max(-1, ...Object.keys(toolAccum).map(Number)) + 1;
144
+ for (const call of calls) {
145
+ toolAccum[index++] = { id: textToolCallId(), name: call.name, argsBuf: JSON.stringify(call.arguments) };
146
+ }
147
+ finishReason = "tool_calls";
148
+ }
103
149
  for (const tb of Object.values(toolAccum)) {
104
150
  let args = {};
105
151
  try {
@@ -110,6 +156,10 @@ export class OpenAIProvider {
110
156
  }
111
157
  yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args, ...malformedToolArguments(tb.argsBuf) };
112
158
  }
159
+ // Surface an output-cap truncation (finish_reason="length") to the runner/kernel as a usage
160
+ // frame, the channel the runner reads stopReason from.
161
+ if (finishReason)
162
+ yield { type: "usage", totalTokens: 0, stopReason: finishReason };
113
163
  }
114
164
  prepareRequest(context, tools, extensions, state) {
115
165
  let requestTools = tools;
@@ -0,0 +1,16 @@
1
+ export interface TextToolCall {
2
+ name: string;
3
+ arguments: Record<string, unknown>;
4
+ }
5
+ /** Index of the first DSML marker in `text`, or -1. */
6
+ export declare function markupStart(text: string): number;
7
+ /** How many trailing characters of `text` could begin a marker and must wait for the next chunk. */
8
+ export declare function partialMarkupLength(text: string): number;
9
+ /** The tool calls in `markup`, or undefined when it is not a complete, well-formed set of calls. */
10
+ export declare function parseTextToolCalls(markup: string): TextToolCall[] | undefined;
11
+ /** A call id for a parsed call: unique, so read-backs and tool results never collide. */
12
+ export declare function textToolCallId(): string;
13
+ /** Markup that does not parse fails like the Anthropic adapter's textual-tool-call guard (SPC-022):
14
+ * same provider code and message, retryable, and the body is never echoed. */
15
+ export declare const TEXTUAL_TOOL_CALL_CODE = "textual_tool_call";
16
+ export declare const TEXTUAL_TOOL_CALL_MESSAGE = "Provider emitted a tool call as text instead of a native tool block";
@@ -0,0 +1,96 @@
1
+ // Plan task 9.0 · tool calls a model wrote as text.
2
+ //
3
+ // DeepSeek sometimes answers with its native tool-call markup (DSML) in the message text instead of
4
+ // the API's `tool_calls` field, often in a degraded form:
5
+ //
6
+ // <|DSML|invoke name="read_file">
7
+ // <|DSML|parameter name="path" string="true">logs/svc-13.log</|DSML|parameter>
8
+ // </|DSML|invoke>
9
+ //
10
+ // (or `||DSML|| parameter` with doubled bars and spaces). Left as text it enters history, the model
11
+ // imitates it on later turns, and the run loops until its turn limit. The dialect layer therefore
12
+ // keeps the markup out of the text: it parses it into real tool calls, or — when it cannot — fails
13
+ // the call as a retryable protocol error so the kernel's recovery asks again.
14
+ const MARKUP_START = /<\s*\/?\s*[||]+\s*DSML/i;
15
+ const TAG = /<\s*(\/?)\s*[||]+\s*DSML\s*[||]+\s*([A-Za-z_]+)([^>]*)>/gi;
16
+ /** A text tail that may be the start of a marker split across stream chunks. */
17
+ const PARTIAL_START = /<\s*\/?\s*(?:[||]+\s*(?:D(?:S(?:M)?)?)?)?$/i;
18
+ const PARTIAL_LOOKBACK = 24;
19
+ /** Index of the first DSML marker in `text`, or -1. */
20
+ export function markupStart(text) {
21
+ const match = MARKUP_START.exec(text);
22
+ return match ? match.index : -1;
23
+ }
24
+ /** How many trailing characters of `text` could begin a marker and must wait for the next chunk. */
25
+ export function partialMarkupLength(text) {
26
+ const tail = text.slice(-PARTIAL_LOOKBACK);
27
+ const at = tail.lastIndexOf("<");
28
+ if (at < 0)
29
+ return 0;
30
+ return PARTIAL_START.test(tail.slice(at)) ? tail.length - at : 0;
31
+ }
32
+ function attribute(attrs, name) {
33
+ const match = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, "i").exec(attrs);
34
+ return match?.[1];
35
+ }
36
+ function parameterValue(raw, isString) {
37
+ if (isString)
38
+ return raw;
39
+ try {
40
+ return JSON.parse(raw);
41
+ }
42
+ catch {
43
+ return raw;
44
+ }
45
+ }
46
+ /** The tool calls in `markup`, or undefined when it is not a complete, well-formed set of calls. */
47
+ export function parseTextToolCalls(markup) {
48
+ const calls = [];
49
+ let current;
50
+ let parameter;
51
+ for (const match of markup.matchAll(TAG)) {
52
+ const [whole, close, rawTag, attrs] = match;
53
+ const tag = rawTag.toLowerCase();
54
+ const at = match.index ?? 0;
55
+ if (tag === "invoke") {
56
+ if (!close) {
57
+ const name = attribute(attrs, "name");
58
+ if (!name || current || parameter)
59
+ return undefined;
60
+ current = { name, arguments: {} };
61
+ }
62
+ else {
63
+ if (!current || parameter)
64
+ return undefined;
65
+ calls.push(current);
66
+ current = undefined;
67
+ }
68
+ }
69
+ else if (tag === "parameter") {
70
+ if (!close) {
71
+ const name = attribute(attrs, "name");
72
+ if (!name || !current || parameter)
73
+ return undefined;
74
+ parameter = { name, isString: attribute(attrs, "string") !== "false", start: at + whole.length };
75
+ }
76
+ else {
77
+ if (!current || !parameter)
78
+ return undefined;
79
+ current.arguments[parameter.name] = parameterValue(markup.slice(parameter.start, at), parameter.isString);
80
+ parameter = undefined;
81
+ }
82
+ }
83
+ // Wrappers (`function_calls`, `tool_calls`, a bare `calls`) carry nothing.
84
+ }
85
+ if (current || parameter || calls.length === 0)
86
+ return undefined;
87
+ return calls;
88
+ }
89
+ /** A call id for a parsed call: unique, so read-backs and tool results never collide. */
90
+ export function textToolCallId() {
91
+ return `call_text_${Math.random().toString(36).slice(2, 12)}`;
92
+ }
93
+ /** Markup that does not parse fails like the Anthropic adapter's textual-tool-call guard (SPC-022):
94
+ * same provider code and message, retryable, and the body is never echoed. */
95
+ export const TEXTUAL_TOOL_CALL_CODE = "textual_tool_call";
96
+ export const TEXTUAL_TOOL_CALL_MESSAGE = "Provider emitted a tool call as text instead of a native tool block";
@@ -1309,6 +1309,7 @@ export class CanonicalRunnerRuntime {
1309
1309
  ...(event.key ? { key: event.key } : {}),
1310
1310
  ...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
1311
1311
  ...(event.pinned ? { pinned: true } : {}),
1312
+ ...(event.inherited_from ? { inherited_from: event.inherited_from } : {}),
1312
1313
  }],
1313
1314
  },
1314
1315
  };
@@ -1607,6 +1608,7 @@ export class CanonicalRunnerRuntime {
1607
1608
  ...(event.key ? { key: event.key } : {}),
1608
1609
  ...(event.tokens !== undefined ? { tokens: Number(event.tokens) } : {}),
1609
1610
  ...(event.pinned ? { pinned: true } : {}),
1611
+ ...(event.inherited_from ? { inherited_from: event.inherited_from } : {}),
1610
1612
  });
1611
1613
  return true;
1612
1614
  case "set_available_skills":
@@ -13,6 +13,10 @@ export interface ContextPolicy {
13
13
  renewalCarryover: number;
14
14
  collapseOldAssistantNarration: boolean;
15
15
  idleMicroCompactMinutes: number;
16
+ /** Plan task 9.1 · tokens the model's notes may hold in the state turn (kernel default 600, on since
17
+ * 0.2.82); 0 turns the `note` tool off. Notes are written by the model and kept verbatim through
18
+ * compaction. */
19
+ notesBudgetTokens?: number;
16
20
  }
17
21
  export interface ContextPolicyWire {
18
22
  pressure_thresholds_ppm: ContextPressureThresholds;
@@ -21,6 +25,7 @@ export interface ContextPolicyWire {
21
25
  renewal_carryover_ppm: number;
22
26
  collapse_old_assistant_narration: boolean;
23
27
  idle_micro_compact_minutes: number;
28
+ notes_budget_tokens?: number;
24
29
  }
25
30
  export interface ContextPolicyOverrides extends Partial<Omit<ContextPolicy, "pressureThresholds">> {
26
31
  pressureThresholds?: Partial<ContextPressureThresholds>;
@@ -42,6 +42,8 @@ export function normalizeContextPolicy(policy) {
42
42
  if (typeof policy.collapseOldAssistantNarration !== "boolean") {
43
43
  throw new TypeError("collapseOldAssistantNarration must be boolean");
44
44
  }
45
+ if (policy.notesBudgetTokens !== undefined)
46
+ assertIntegerAtLeast(policy.notesBudgetTokens, 0, "notesBudgetTokens");
45
47
  return {
46
48
  pressure_thresholds_ppm,
47
49
  target_after_compress_ppm,
@@ -49,6 +51,7 @@ export function normalizeContextPolicy(policy) {
49
51
  renewal_carryover_ppm: ratioToPpm(policy.renewalCarryover, "renewalCarryover"),
50
52
  collapse_old_assistant_narration: policy.collapseOldAssistantNarration,
51
53
  idle_micro_compact_minutes: policy.idleMicroCompactMinutes,
54
+ ...(policy.notesBudgetTokens !== undefined ? { notes_budget_tokens: policy.notesBudgetTokens } : {}),
52
55
  };
53
56
  }
54
57
  export function ratioToPpm(value, field = "ratio") {
@@ -1,7 +1,7 @@
1
1
  // Mirrors the kernel's EXPOSURE_EXEMPT_META_TOOLS invariant: kernel-owned meta surfaces are
2
2
  // never narrowed away by a tool allow-list. `read_result` is runner-resolved before reaching any
3
3
  // plane today, but the lists must not drift.
4
- const DEFAULT_META_TOOLS = new Set(["skill", "memory", "knowledge", "update_plan", "read_result"]);
4
+ const DEFAULT_META_TOOLS = new Set(["skill", "memory", "knowledge", "update_plan", "note", "read_result"]);
5
5
  /** Wraps an execution plane, allowing only manifest-permitted tool IDs (+ meta-tools). */
6
6
  export class FilteredExecutionPlane {
7
7
  inner;
@@ -84,6 +84,16 @@ function projectKernelObservation(obs, turn, opts = {}) {
84
84
  ...(obs.history && obs.history.length > 0 ? { history: obs.history } : {}),
85
85
  ...(obs.history_items && obs.history_items.length > 0 ? { history_items: obs.history_items } : {}),
86
86
  ...(obs.history_items_omitted ? { history_items_omitted: obs.history_items_omitted } : {}),
87
+ ...(obs.edges && obs.edges.length > 0 ? { edges: obs.edges } : {}),
88
+ ...(obs.edges_omitted ? { edges_omitted: obs.edges_omitted } : {}),
89
+ ...(obs.waste_items && obs.waste_items.length > 0 ? { waste_items: obs.waste_items } : {}),
90
+ ...(obs.external_edges && obs.external_edges.length > 0 ? { external_edges: obs.external_edges } : {}),
91
+ ...(obs.parent_items_used && obs.parent_items_used.length > 0 ? { parent_items_used: obs.parent_items_used } : {}),
92
+ ...(obs.prefix_breaks && obs.prefix_breaks.length > 0 ? { prefix_breaks: obs.prefix_breaks } : {}),
93
+ ...(obs.prefix_breaks_omitted ? { prefix_breaks_omitted: obs.prefix_breaks_omitted } : {}),
94
+ ...(obs.cache_tail_tokens ? { cache_tail_tokens: obs.cache_tail_tokens } : {}),
95
+ ...(obs.notes_written ? { notes_written: obs.notes_written } : {}),
96
+ ...(obs.notes_active ? { notes_active: obs.notes_active } : {}),
87
97
  };
88
98
  case "rollbacked":
89
99
  return {
@@ -1,5 +1,5 @@
1
1
  import type { ModelMessage, RenderedContext, ToolCall, ToolExecutionResult, ToolSchema } from "../types.js";
2
- import type { ContextHistoryItemRecord, ContextItemRecord, ContextOriginTally, RollbackReason } from "./session-log.js";
2
+ import type { ContextExternalEdge, ContextExternalItem, ContextHistoryItemRecord, ContextPrefixBreak, ContextItemEdge, ContextItemRecord, ContextOriginTally, RollbackReason } from "./session-log.js";
3
3
  interface TaskUpdate {
4
4
  plan?: string[];
5
5
  currentStep?: number;
@@ -139,6 +139,16 @@ export interface KernelObservation {
139
139
  history?: ContextOriginTally[];
140
140
  history_items?: ContextHistoryItemRecord[];
141
141
  history_items_omitted?: number;
142
+ edges?: ContextItemEdge[];
143
+ edges_omitted?: number;
144
+ waste_items?: number[];
145
+ external_edges?: ContextExternalEdge[];
146
+ parent_items_used?: ContextExternalItem[];
147
+ prefix_breaks?: ContextPrefixBreak[];
148
+ prefix_breaks_omitted?: number;
149
+ cache_tail_tokens?: number;
150
+ notes_written?: number;
151
+ notes_active?: number;
142
152
  archived_count?: number;
143
153
  turn?: number;
144
154
  checkpoint_history_len?: number;
@@ -766,6 +766,7 @@ export class RuntimeRunner {
766
766
  ...(entry.key ? { key: entry.key } : {}),
767
767
  tokens: entry.tokens ?? Math.max(1, Math.ceil(entry.content.length / 4)),
768
768
  ...(entry.pinned ? { pinned: true } : {}),
769
+ ...(entry.inherited_from ? { inherited_from: entry.inherited_from } : {}),
769
770
  });
770
771
  }
771
772
  if (this.opts.initialMemory) {
@@ -77,7 +77,7 @@ export declare const SESSION_EVENT_REGISTRY: {
77
77
  };
78
78
  readonly context_lifecycle_reported: {
79
79
  readonly required: readonly ["cache_loss_tokens", "knowledge_entries", "knowledge_referenced", "recall_expanded", "recalled", "regret", "turn", "waste_tokens"];
80
- readonly optional: readonly ["history", "history_items", "history_items_omitted", "items"];
80
+ readonly optional: readonly ["cache_tail_tokens", "edges", "edges_omitted", "external_edges", "history", "history_items", "history_items_omitted", "items", "notes_active", "notes_written", "parent_items_used", "prefix_breaks", "prefix_breaks_omitted", "waste_items"];
81
81
  };
82
82
  readonly suspended: {
83
83
  readonly required: readonly ["reason", "turn"];
@@ -21,7 +21,7 @@ export const SESSION_EVENT_REGISTRY = {
21
21
  rollbacked: { required: ["checkpoint_history_len", "turn"], optional: ["reason"] },
22
22
  capability_changed: { required: ["added", "removed", "turn"], optional: ["capability_id", "change_kind", "mount_reason", "mounted_by", "version"] },
23
23
  context_renewed: { required: ["handoff_ref", "sprint", "turn"], optional: [] },
24
- context_lifecycle_reported: { required: ["cache_loss_tokens", "knowledge_entries", "knowledge_referenced", "recall_expanded", "recalled", "regret", "turn", "waste_tokens"], optional: ["history", "history_items", "history_items_omitted", "items"] },
24
+ context_lifecycle_reported: { required: ["cache_loss_tokens", "knowledge_entries", "knowledge_referenced", "recall_expanded", "recalled", "regret", "turn", "waste_tokens"], optional: ["cache_tail_tokens", "edges", "edges_omitted", "external_edges", "history", "history_items", "history_items_omitted", "items", "notes_active", "notes_written", "parent_items_used", "prefix_breaks", "prefix_breaks_omitted", "waste_items"] },
25
25
  suspended: { required: ["reason", "turn"], optional: ["pending_calls"] },
26
26
  resumed: { required: ["turn"], optional: ["approved", "denied"] },
27
27
  tool_gated: { required: ["call_id", "reason", "tool", "turn"], optional: [] },
@@ -53,14 +53,18 @@ export interface ContextOriginTally {
53
53
  }
54
54
  /** One knowledge item's lifecycle in a `context_lifecycle_reported` event (plan task 4.1). */
55
55
  export interface ContextItemRecord {
56
+ /** The item's id, shared with history items and item-graph edges. */
57
+ id?: number;
56
58
  key?: string;
57
- origin: "system" | "skill" | "memory_recall" | "reference" | "blackboard" | "tool_result" | "dependency_output" | "user" | "assistant" | "task_state" | "signal" | "provider_summary";
59
+ origin: "system" | "skill" | "memory_recall" | "reference" | "blackboard" | "tool_result" | "dependency_output" | "user" | "assistant" | "task_state" | "signal" | "provider_summary" | "note";
58
60
  tokens: number;
59
61
  value: {
60
62
  injected_turn: number;
61
63
  ref_count: number;
62
64
  last_referenced_step?: number;
63
65
  last_referenced_turn?: number;
66
+ strong_refs?: number;
67
+ lexical_refs?: number;
64
68
  expanded?: boolean;
65
69
  paged_in_after_evict?: boolean;
66
70
  };
@@ -68,6 +72,35 @@ export interface ContextItemRecord {
68
72
  end?: "evicted" | "expired" | "removed" | "compacted" | "renewed";
69
73
  }
70
74
  /** One history item's lifecycle in a `context_lifecycle_reported` event (plan task 4.1). */
75
+ /** One directed edge of the item graph: `from` answers, references, reads back or summarizes `to`. */
76
+ export interface ContextItemEdge {
77
+ from: number;
78
+ to: number;
79
+ relation: "answers" | "references_strong" | "references_lexical" | "reads_back" | "summarizes" | "expands" | "derived_from" | "links_to" | "supersedes";
80
+ turn: number;
81
+ }
82
+ /** An item of another run: a parent operation's item, or a workflow dependency's output. */
83
+ export interface ContextExternalItem {
84
+ operation?: string;
85
+ agent?: string;
86
+ item?: number;
87
+ }
88
+ /** Plan task 8.0 · where a provider request broke the previous request's cacheable prefix. */
89
+ export interface ContextPrefixBreak {
90
+ turn: number;
91
+ cause: "toolset" | "system" | "promotion" | "collapse" | "reprojection" | "narration" | "budget_drop" | "snip" | "micro" | "auto" | "renewal" | "provider" | "other";
92
+ /** Prefix tokens of the previous request still cacheable. */
93
+ kept_tokens: number;
94
+ /** Prefix tokens of the previous request after the break: sent uncached again. */
95
+ lost_tokens: number;
96
+ }
97
+ /** An edge from an item of this run to an item of another run. */
98
+ export interface ContextExternalEdge {
99
+ from: number;
100
+ to: ContextExternalItem;
101
+ relation: "inherited_from" | "depends_on";
102
+ turn: number;
103
+ }
71
104
  export interface ContextHistoryItemRecord {
72
105
  id: number;
73
106
  origin: ContextItemRecord["origin"];
@@ -216,6 +249,23 @@ export type SessionEvent = {
216
249
  /** The latest history items one by one (at most 128); older ones are only counted. */
217
250
  history_items?: ContextHistoryItemRecord[];
218
251
  history_items_omitted?: number;
252
+ /** The item graph: resident edges, then the latest edges touching an item that left. */
253
+ edges?: ContextItemEdge[];
254
+ edges_omitted?: number;
255
+ /** Knowledge item ids nothing referenced or read back. */
256
+ waste_items?: number[];
257
+ /** Edges to items of other runs: inherited copies and dependency reads. */
258
+ external_edges?: ContextExternalEdge[];
259
+ /** The parent run's items whose inherited copies this run used. */
260
+ parent_items_used?: ContextExternalItem[];
261
+ /** Plan task 8.0 · the latest prefix breaks between provider requests (`cache_loss_tokens` sums every break); older ones are only counted. */
262
+ prefix_breaks?: ContextPrefixBreak[];
263
+ prefix_breaks_omitted?: number;
264
+ /** State-turn tokens of every request a later one followed: never reusable, so not a break, but part of a provider's uncached input count. */
265
+ cache_tail_tokens?: number;
266
+ /** Plan task 9.1 · notes the model wrote (rewrites count again) and notes it holds now. */
267
+ notes_written?: number;
268
+ notes_active?: number;
219
269
  } | {
220
270
  kind: "suspended";
221
271
  turn: number;
@@ -214,6 +214,12 @@ export interface InheritedKnowledge {
214
214
  key?: string;
215
215
  tokens?: number;
216
216
  pinned?: boolean;
217
+ /** Plan task 7.4 · the parent run's item this is a copy of; reported as an `inherited_from` edge. */
218
+ inherited_from?: {
219
+ operation?: string;
220
+ agent?: string;
221
+ item?: number;
222
+ };
217
223
  }
218
224
  /** Per-node spawn descriptor carried in the `workflow_batch_spawned` observation. */
219
225
  export interface WorkflowSpawnInfo {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstrike/wasm",
3
- "version": "0.2.80",
3
+ "version": "0.2.82",
4
4
  "description": "DeepStrike WASM SDK — browser, Cloudflare Workers, Deno Deploy",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -14,10 +14,10 @@
14
14
  "build:wasm": "wasm-pack build ../crates/deepstrike-wasm --target bundler --out-dir ../../wasm/pkg",
15
15
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
16
16
  "test": "node --experimental-vm-modules node_modules/.bin/jest",
17
- "test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs && node tests/golden-fixtures-sweep.node.cjs && npm run build && node tests/boundary-p0-regressions.node.mjs && node tests/boundary-p1-regressions.node.mjs && node tests/boundary-p2-regressions.node.mjs && node tests/memory-candidate-binding.node.mjs && node tests/memory-recall-renewal.node.mjs && node tests/dynamic-workflow.node.mjs"
17
+ "test:canonical-binding": "wasm-pack build ../crates/deepstrike-wasm --target nodejs --dev --out-dir ../../wasm/pkg-node && node tests/canonical-binding.node.cjs && node tests/golden-fixtures-sweep.node.cjs && npm run build && node tests/boundary-p0-regressions.node.mjs && node tests/boundary-p1-regressions.node.mjs && node tests/boundary-p2-regressions.node.mjs && node tests/memory-candidate-binding.node.mjs && node tests/memory-recall-renewal.node.mjs && node tests/dynamic-workflow.node.mjs && node tests/notes.node.mjs && node tests/notes.node.mjs"
18
18
  },
19
19
  "dependencies": {
20
- "@deepstrike/wasm-kernel": "0.2.80"
20
+ "@deepstrike/wasm-kernel": "0.2.82"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/jest": "^30.0.0",