@deepstrike/sdk 0.2.51 → 0.2.60

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 (148) hide show
  1. package/README.md +28 -28
  2. package/dist/agent-ir.d.ts +103 -0
  3. package/dist/agent-ir.js +134 -0
  4. package/dist/agent.d.ts +67 -0
  5. package/dist/agent.js +36 -0
  6. package/dist/collaboration/harness.js +1 -1
  7. package/dist/collaboration/modes/creator-verifier.d.ts +2 -7
  8. package/dist/collaboration/modes/creator-verifier.js +4 -6
  9. package/dist/collaboration/pool.d.ts +8 -20
  10. package/dist/collaboration/pool.js +27 -97
  11. package/dist/compat/anthropic/mcp.d.ts +15 -0
  12. package/dist/compat/anthropic/mcp.js +10 -0
  13. package/dist/compat/openai/agent.d.ts +34 -0
  14. package/dist/compat/openai/agent.js +24 -0
  15. package/dist/governance.d.ts +1 -17
  16. package/dist/governance.js +1 -34
  17. package/dist/guardrail.d.ts +6 -0
  18. package/dist/guardrail.js +1 -0
  19. package/dist/handoff-target.d.ts +12 -0
  20. package/dist/handoff-target.js +1 -0
  21. package/dist/harness/manifest.js +0 -4
  22. package/dist/index.d.ts +19 -7
  23. package/dist/index.js +8 -5
  24. package/dist/kernel.d.ts +2 -20
  25. package/dist/knowledge/public.d.ts +29 -0
  26. package/dist/knowledge/public.js +1 -0
  27. package/dist/mcp-server.d.ts +28 -0
  28. package/dist/mcp-server.js +1 -0
  29. package/dist/memory/agent.d.ts +2 -2
  30. package/dist/memory/agent.js +2 -2
  31. package/dist/memory/durable.d.ts +16 -0
  32. package/dist/memory/durable.js +46 -0
  33. package/dist/memory/in-memory-store.d.ts +9 -7
  34. package/dist/memory/in-memory-store.js +8 -2
  35. package/dist/memory/protocols.d.ts +22 -4
  36. package/dist/memory/public.d.ts +4 -3
  37. package/dist/memory/public.js +3 -2
  38. package/dist/providers/anthropic-adapter.d.ts +47 -0
  39. package/dist/providers/anthropic-adapter.js +444 -0
  40. package/dist/providers/anthropic-compatible.d.ts +2 -3
  41. package/dist/providers/anthropic-compatible.js +8 -5
  42. package/dist/providers/anthropic.d.ts +19 -23
  43. package/dist/providers/anthropic.js +169 -395
  44. package/dist/providers/base.js +43 -3
  45. package/dist/providers/capability-router.d.ts +29 -0
  46. package/dist/providers/capability-router.js +43 -0
  47. package/dist/providers/catalog.d.ts +16 -4
  48. package/dist/providers/catalog.js +112 -21
  49. package/dist/providers/content-normalization.d.ts +57 -0
  50. package/dist/providers/content-normalization.js +238 -0
  51. package/dist/providers/content-policy.d.ts +16 -0
  52. package/dist/providers/content-policy.js +39 -0
  53. package/dist/providers/credentials.d.ts +83 -0
  54. package/dist/providers/credentials.js +190 -0
  55. package/dist/providers/endpoints.d.ts +137 -0
  56. package/dist/providers/endpoints.js +128 -0
  57. package/dist/providers/factories.js +9 -3
  58. package/dist/providers/gemini-adapter.d.ts +33 -0
  59. package/dist/providers/gemini-adapter.js +272 -0
  60. package/dist/providers/gemini.d.ts +16 -3
  61. package/dist/providers/gemini.js +97 -195
  62. package/dist/providers/model-catalog.d.ts +37 -0
  63. package/dist/providers/model-catalog.js +62 -0
  64. package/dist/providers/model-registry.d.ts +98 -0
  65. package/dist/providers/model-registry.js +315 -0
  66. package/dist/providers/ollama-adapter.d.ts +65 -0
  67. package/dist/providers/ollama-adapter.js +188 -0
  68. package/dist/providers/ollama.d.ts +9 -4
  69. package/dist/providers/ollama.js +96 -109
  70. package/dist/providers/openai-chat-dialects.d.ts +154 -0
  71. package/dist/providers/openai-chat-dialects.js +179 -0
  72. package/dist/providers/openai-chat.d.ts +46 -18
  73. package/dist/providers/openai-chat.js +418 -51
  74. package/dist/providers/openai-responses-adapter.d.ts +42 -0
  75. package/dist/providers/openai-responses-adapter.js +341 -0
  76. package/dist/providers/openai-responses.d.ts +14 -33
  77. package/dist/providers/openai-responses.js +131 -264
  78. package/dist/providers/openai.d.ts +29 -76
  79. package/dist/providers/openai.js +195 -292
  80. package/dist/providers/protocol-adapter.d.ts +34 -0
  81. package/dist/providers/protocol-adapter.js +9 -0
  82. package/dist/providers/protocol-capabilities.d.ts +34 -0
  83. package/dist/providers/protocol-capabilities.js +44 -0
  84. package/dist/providers/provider-error.d.ts +31 -0
  85. package/dist/providers/provider-error.js +149 -0
  86. package/dist/providers/public.d.ts +26 -3
  87. package/dist/providers/public.js +13 -1
  88. package/dist/providers/registry.d.ts +7 -6
  89. package/dist/providers/registry.js +47 -20
  90. package/dist/providers/request-plan.d.ts +86 -0
  91. package/dist/providers/request-plan.js +175 -0
  92. package/dist/providers/usage-normalizer.d.ts +48 -0
  93. package/dist/providers/usage-normalizer.js +96 -0
  94. package/dist/providers/vendor-profiles.d.ts +2 -15
  95. package/dist/providers/vendor-profiles.js +14 -60
  96. package/dist/runtime/canonical-kernel-step.d.ts +10 -2
  97. package/dist/runtime/canonical-kernel-step.js +106 -32
  98. package/dist/runtime/context-policy.d.ts +10 -12
  99. package/dist/runtime/context-policy.js +6 -8
  100. package/dist/runtime/durable-content.d.ts +50 -0
  101. package/dist/runtime/durable-content.js +159 -0
  102. package/dist/runtime/execution-plane.d.ts +2 -2
  103. package/dist/runtime/execution-plane.js +2 -2
  104. package/dist/runtime/kernel-event-log.js +0 -1
  105. package/dist/runtime/kernel-step.d.ts +0 -1
  106. package/dist/runtime/kernel-step.js +4 -2
  107. package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
  108. package/dist/runtime/mcp-proxy-plane.js +44 -6
  109. package/dist/runtime/output-schema.d.ts +1 -2
  110. package/dist/runtime/provider-replay.d.ts +1 -1
  111. package/dist/runtime/provider-replay.js +12 -27
  112. package/dist/runtime/reactive-session.d.ts +1 -1
  113. package/dist/runtime/reactive-session.js +2 -3
  114. package/dist/runtime/run-group.d.ts +1 -1
  115. package/dist/runtime/runner.d.ts +26 -40
  116. package/dist/runtime/runner.js +145 -63
  117. package/dist/runtime/session-log.d.ts +8 -1
  118. package/dist/runtime/session-log.js +42 -2
  119. package/dist/runtime/session-repair.d.ts +1 -1
  120. package/dist/runtime/session-repair.js +1 -1
  121. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  122. package/dist/runtime/sub-agent-orchestrator.js +8 -11
  123. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  124. package/dist/runtime/workflow-control-flow.js +0 -16
  125. package/dist/session.d.ts +11 -0
  126. package/dist/session.js +1 -0
  127. package/dist/skill.d.ts +17 -0
  128. package/dist/skill.js +16 -0
  129. package/dist/skills/loader.d.ts +3 -0
  130. package/dist/tools/errors.d.ts +1 -3
  131. package/dist/tools/errors.js +1 -3
  132. package/dist/tools/index.d.ts +3 -0
  133. package/dist/types/agent.d.ts +21 -9
  134. package/dist/types/agent.js +30 -4
  135. package/dist/types.d.ts +128 -13
  136. package/package.json +3 -3
  137. package/dist/providers/deepseek.d.ts +0 -46
  138. package/dist/providers/deepseek.js +0 -97
  139. package/dist/providers/glm.d.ts +0 -25
  140. package/dist/providers/glm.js +0 -48
  141. package/dist/providers/kimi.d.ts +0 -23
  142. package/dist/providers/kimi.js +0 -30
  143. package/dist/providers/minimax.d.ts +0 -49
  144. package/dist/providers/minimax.js +0 -98
  145. package/dist/providers/profiles.d.ts +0 -1992
  146. package/dist/providers/profiles.js +0 -796
  147. package/dist/providers/qwen.d.ts +0 -38
  148. package/dist/providers/qwen.js +0 -97
@@ -1,6 +1,5 @@
1
- export const CONTEXT_POLICY_VERSION = 1;
2
1
  export const PPM_SCALE = 1_000_000;
3
- export const DEFAULT_CONTEXT_POLICY_V1 = Object.freeze({
2
+ export const DEFAULT_CONTEXT_POLICY = Object.freeze({
4
3
  pressureThresholds: Object.freeze({ snip: 0.70, micro: 0.80, collapse: 0.90, auto: 0.95, renewal: 0.98 }),
5
4
  targetAfterCompress: 0.65,
6
5
  preserveRecentTurns: 2,
@@ -9,20 +8,20 @@ export const DEFAULT_CONTEXT_POLICY_V1 = Object.freeze({
9
8
  idleMicroCompactMinutes: 60,
10
9
  });
11
10
  /** Resolve ergonomic partial SDK options into one complete, atomically validated policy. */
12
- export function contextPolicyV1(overrides = {}) {
11
+ export function contextPolicy(overrides = {}) {
13
12
  const policy = {
14
- ...DEFAULT_CONTEXT_POLICY_V1,
13
+ ...DEFAULT_CONTEXT_POLICY,
15
14
  ...overrides,
16
15
  pressureThresholds: {
17
- ...DEFAULT_CONTEXT_POLICY_V1.pressureThresholds,
16
+ ...DEFAULT_CONTEXT_POLICY.pressureThresholds,
18
17
  ...overrides.pressureThresholds,
19
18
  },
20
19
  };
21
- normalizeContextPolicyV1(policy);
20
+ normalizeContextPolicy(policy);
22
21
  return policy;
23
22
  }
24
23
  /** Convert the public ratio-based policy to the canonical integer-only ABI wire shape. */
25
- export function normalizeContextPolicyV1(policy) {
24
+ export function normalizeContextPolicy(policy) {
26
25
  const pressure_thresholds_ppm = {
27
26
  snip: ratioToPpm(policy.pressureThresholds.snip, "pressureThresholds.snip"),
28
27
  micro: ratioToPpm(policy.pressureThresholds.micro, "pressureThresholds.micro"),
@@ -44,7 +43,6 @@ export function normalizeContextPolicyV1(policy) {
44
43
  throw new TypeError("collapseOldAssistantNarration must be boolean");
45
44
  }
46
45
  return {
47
- version: CONTEXT_POLICY_VERSION,
48
46
  pressure_thresholds_ppm,
49
47
  target_after_compress_ppm,
50
48
  preserve_recent_turns: policy.preserveRecentTurns,
@@ -0,0 +1,50 @@
1
+ /** Provider-neutral durable content ABI.
2
+ *
3
+ * This is deliberately separate from provider-facing ContentPart. Durable records carry only
4
+ * portable content and explicit payload locators; protocol adapters decide how to serialize it.
5
+ */
6
+ export declare class DurableContentError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ export type DurableSource = {
10
+ kind: "url";
11
+ url: string;
12
+ } | {
13
+ kind: "base64";
14
+ data: string;
15
+ } | {
16
+ kind: "file_id";
17
+ id: string;
18
+ affinity: {
19
+ provider_id: string;
20
+ endpoint_id: string;
21
+ };
22
+ } | {
23
+ kind: "object";
24
+ handle: string;
25
+ owner: string;
26
+ payload_ref: string;
27
+ };
28
+ export type DurableContentBlock = {
29
+ type: "text";
30
+ text: string;
31
+ } | {
32
+ type: "image" | "audio" | "video" | "file";
33
+ source: DurableSource;
34
+ media_type?: string;
35
+ provider_options?: Record<string, unknown>;
36
+ };
37
+ export interface DurableContent {
38
+ blocks: DurableContentBlock[];
39
+ }
40
+ export interface DurableToolResult extends DurableContent {
41
+ call_id: string;
42
+ is_error: boolean;
43
+ }
44
+ import type { ToolOutputBlock } from "../types.js";
45
+ export declare function decodeDurableContent(value: unknown): DurableContent;
46
+ export declare function decodeDurableToolResult(value: unknown): DurableToolResult;
47
+ export declare function encodeDurableContent(content: DurableContent): Record<string, unknown>;
48
+ export declare function encodeDurableToolResult(result: DurableToolResult): Record<string, unknown>;
49
+ export declare function toolOutputBlocksToDurable(blocks: readonly ToolOutputBlock[]): DurableContentBlock[];
50
+ export declare function durableBlocksToToolOutput(blocks: readonly DurableContentBlock[]): ToolOutputBlock[];
@@ -0,0 +1,159 @@
1
+ /** Provider-neutral durable content ABI.
2
+ *
3
+ * This is deliberately separate from provider-facing ContentPart. Durable records carry only
4
+ * portable content and explicit payload locators; protocol adapters decide how to serialize it.
5
+ */
6
+ export class DurableContentError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "DurableContentError";
10
+ }
11
+ }
12
+ const CONTENT_KEYS = new Set(["blocks"]);
13
+ const TOOL_RESULT_KEYS = new Set(["call_id", "is_error", "blocks"]);
14
+ const SOURCE_KEYS = {
15
+ url: new Set(["kind", "url"]),
16
+ base64: new Set(["kind", "data"]),
17
+ file_id: new Set(["kind", "id", "affinity"]),
18
+ object: new Set(["kind", "handle", "owner", "payload_ref"]),
19
+ };
20
+ function object(value, label) {
21
+ if (!value || typeof value !== "object" || Array.isArray(value))
22
+ throw new DurableContentError(`${label} must be an object`);
23
+ return value;
24
+ }
25
+ function exactKeys(value, allowed, label) {
26
+ for (const key of Object.keys(value))
27
+ if (!allowed.has(key))
28
+ throw new DurableContentError(`${label} has unknown field ${key}`);
29
+ }
30
+ function requiredString(value, label) {
31
+ if (typeof value !== "string" || value.length === 0)
32
+ throw new DurableContentError(`${label} must be a non-empty string`);
33
+ return value;
34
+ }
35
+ function requiredBoolean(value, label) {
36
+ if (typeof value !== "boolean")
37
+ throw new DurableContentError(`${label} must be a boolean`);
38
+ return value;
39
+ }
40
+ function base64(value, label) {
41
+ const data = requiredString(value, label);
42
+ if (data.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data)) {
43
+ throw new DurableContentError(`${label} is not valid base64`);
44
+ }
45
+ return data;
46
+ }
47
+ function source(value, label) {
48
+ const raw = object(value, `${label} source`);
49
+ const kind = raw.kind;
50
+ if (kind !== "url" && kind !== "base64" && kind !== "file_id" && kind !== "object")
51
+ throw new DurableContentError(`${label} source kind is invalid`);
52
+ exactKeys(raw, SOURCE_KEYS[kind], `${label} source`);
53
+ if (kind === "url")
54
+ return { kind, url: requiredString(raw.url, `${label} URL`) };
55
+ if (kind === "base64")
56
+ return { kind, data: base64(raw.data, `${label} base64 data`) };
57
+ if (kind === "file_id") {
58
+ const id = requiredString(raw.id, `${label} file id`);
59
+ const a = object(raw.affinity, `${label} affinity`);
60
+ exactKeys(a, new Set(["provider_id", "endpoint_id"]), `${label} affinity`);
61
+ return {
62
+ kind,
63
+ id,
64
+ affinity: {
65
+ provider_id: requiredString(a.provider_id, `${label} affinity provider_id`),
66
+ endpoint_id: requiredString(a.endpoint_id, `${label} affinity endpoint_id`),
67
+ },
68
+ };
69
+ }
70
+ return {
71
+ kind,
72
+ handle: requiredString(raw.handle, `${label} object handle`),
73
+ owner: requiredString(raw.owner, `${label} object owner`),
74
+ payload_ref: requiredString(raw.payload_ref, `${label} payload_ref`),
75
+ };
76
+ }
77
+ function block(value, index) {
78
+ const raw = object(value, `content block ${index}`);
79
+ const type = raw.type;
80
+ if (type === "text") {
81
+ exactKeys(raw, new Set(["type", "text"]), `content block ${index}`);
82
+ return { type, text: typeof raw.text === "string" ? raw.text : (() => { throw new DurableContentError(`content block ${index} text must be a string`); })() };
83
+ }
84
+ if (type === "tool_result")
85
+ throw new DurableContentError("nested tool_result blocks are forbidden");
86
+ if (type !== "image" && type !== "audio" && type !== "video" && type !== "file")
87
+ throw new DurableContentError(`unknown content block type: ${String(type)}`);
88
+ exactKeys(raw, new Set(["type", "source", "media_type", "provider_options"]), `content block ${index}`);
89
+ const mediaType = raw.media_type === undefined ? undefined : requiredString(raw.media_type, `content block ${index} media_type`);
90
+ let providerOptions;
91
+ if (raw.provider_options !== undefined)
92
+ providerOptions = object(raw.provider_options, `content block ${index} provider_options`);
93
+ return {
94
+ type,
95
+ source: source(raw.source, `content block ${index}`),
96
+ ...(mediaType === undefined ? {} : { media_type: mediaType }),
97
+ ...(providerOptions === undefined ? {} : { provider_options: providerOptions }),
98
+ };
99
+ }
100
+ function blocks(value) {
101
+ if (!Array.isArray(value))
102
+ throw new DurableContentError("content blocks must be an array");
103
+ return value.map(block);
104
+ }
105
+ export function decodeDurableContent(value) {
106
+ const raw = object(value, "durable content");
107
+ exactKeys(raw, CONTENT_KEYS, "durable content");
108
+ return { blocks: blocks(raw.blocks) };
109
+ }
110
+ export function decodeDurableToolResult(value) {
111
+ const raw = object(value, "durable tool result");
112
+ exactKeys(raw, TOOL_RESULT_KEYS, "durable tool result");
113
+ return { call_id: requiredString(raw.call_id, "tool result call_id"), is_error: requiredBoolean(raw.is_error, "tool result is_error"), blocks: blocks(raw.blocks) };
114
+ }
115
+ export function encodeDurableContent(content) {
116
+ const decoded = decodeDurableContent(content);
117
+ return { blocks: decoded.blocks };
118
+ }
119
+ export function encodeDurableToolResult(result) {
120
+ const decoded = decodeDurableToolResult(result);
121
+ return { call_id: decoded.call_id, is_error: decoded.is_error, blocks: decoded.blocks };
122
+ }
123
+ export function toolOutputBlocksToDurable(blocks) {
124
+ return blocks.map((part, index) => {
125
+ if (part.type === "text")
126
+ return { type: "text", text: part.text };
127
+ const source = part.source;
128
+ const durableSource = source.kind === "fileId"
129
+ ? {
130
+ kind: "file_id",
131
+ id: source.id,
132
+ affinity: source.affinity
133
+ ? { provider_id: source.affinity.providerId, endpoint_id: source.affinity.endpointId }
134
+ : (() => { throw new DurableContentError(`tool output block ${index} fileId source requires endpoint affinity`); })(),
135
+ }
136
+ : source.kind === "base64" ? { kind: "base64", data: source.data }
137
+ : source.kind === "url" ? { kind: "url", url: source.url }
138
+ : {
139
+ kind: "object",
140
+ handle: source.handle,
141
+ owner: source.owner ?? (() => { throw new DurableContentError(`tool output block ${index} object source requires owner`); })(),
142
+ payload_ref: source.payloadRef ?? (() => { throw new DurableContentError(`tool output block ${index} object source requires payloadRef`); })(),
143
+ };
144
+ return { type: part.type, source: durableSource, ...(part.mediaType ? { media_type: part.mediaType } : {}), ...(part.providerOptions ? { provider_options: part.providerOptions } : {}) };
145
+ });
146
+ }
147
+ export function durableBlocksToToolOutput(blocks) {
148
+ return blocks.map((part) => {
149
+ if (part.type === "text")
150
+ return part;
151
+ const source = part.source;
152
+ const sdkSource = source.kind === "file_id"
153
+ ? { kind: "fileId", id: source.id, affinity: { providerId: source.affinity.provider_id, endpointId: source.affinity.endpoint_id } }
154
+ : source.kind === "base64" ? source
155
+ : source.kind === "url" ? source
156
+ : { kind: "object", handle: source.handle, owner: source.owner, payloadRef: source.payload_ref };
157
+ return { type: part.type, source: sdkSource, ...(part.media_type ? { mediaType: part.media_type } : {}), ...(part.provider_options ? { providerOptions: part.provider_options } : {}) };
158
+ });
159
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ToolCall, ToolSchema, StreamEvent, ToolSuspendEvent, PermissionRequestEvent, PermissionResponse } from "../types.js";
2
2
  import type { RegisteredTool } from "../tools/index.js";
3
- import type { DreamStore, MemoryScope } from "../memory/protocols.js";
3
+ import type { MemoryStore, MemoryScope } from "../memory/protocols.js";
4
4
  import type { KnowledgeSource } from "../knowledge/source.js";
5
5
  import type { OperationContext } from "./reliability.js";
6
6
  export interface RunContext {
@@ -9,7 +9,7 @@ export interface RunContext {
9
9
  agentId?: string;
10
10
  memoryScope?: MemoryScope;
11
11
  skillDir?: string;
12
- dreamStore?: DreamStore;
12
+ memoryStore?: MemoryStore;
13
13
  knowledgeSource?: KnowledgeSource;
14
14
  onToolSuspend?: (event: ToolSuspendEvent) => Promise<unknown> | unknown;
15
15
  onPermissionRequest?: (event: PermissionRequestEvent) => Promise<PermissionResponse | boolean> | PermissionResponse | boolean;
@@ -29,8 +29,8 @@ export class LocalExecutionPlane {
29
29
  for (const c of memoryCalls) {
30
30
  const args = tryParseJson(c.arguments);
31
31
  const topK = typeof args?.top_k === "number" ? args.top_k : 5;
32
- const entries = (ctx.dreamStore && ctx.agentId && ctx.memoryScope)
33
- ? await ctx.dreamStore.search(ctx.agentId, {
32
+ const entries = (ctx.memoryStore && ctx.agentId && ctx.memoryScope)
33
+ ? await ctx.memoryStore.search(ctx.agentId, {
34
34
  scope: ctx.memoryScope,
35
35
  query: String(args?.query ?? ""),
36
36
  top_k: topK,
@@ -91,7 +91,6 @@ export function kernelObservationToSessionEvent(obs, turn, opts = {}) {
91
91
  kind: "entropy_sample",
92
92
  turn: t,
93
93
  score: obs.score ?? 0,
94
- score_version: obs.score_version ?? 0,
95
94
  rho: obs.rho ?? 0,
96
95
  repeat_pressure: obs.repeat_pressure ?? 0,
97
96
  failure_rate: obs.failure_rate ?? 0,
@@ -194,7 +194,6 @@ export interface KernelObservation {
194
194
  /** nodes_rejected. */
195
195
  node_index?: number;
196
196
  score?: number;
197
- score_version?: number;
198
197
  rho?: number;
199
198
  repeat_pressure?: number;
200
199
  failure_rate?: number;
@@ -1,4 +1,4 @@
1
- const CANONICAL_CONTENT_PARTS_PREFIX = "[[deepstrike-content-parts:v1]]";
1
+ const CANONICAL_CONTENT_PARTS_PREFIX = "[[deepstrike-content-parts]]";
2
2
  export function encodeCanonicalContentParts(parts) {
3
3
  return `${CANONICAL_CONTENT_PARTS_PREFIX}${Buffer.from(JSON.stringify(parts)).toString("base64url")}`;
4
4
  }
@@ -43,6 +43,9 @@ export function skillMetadataToKernel(skill) {
43
43
  // P1-B: forward declared tool ids (additive; omitted when empty so existing skills' wire is unchanged).
44
44
  if (skill.allowedTools?.length)
45
45
  out.allowed_tools = skill.allowedTools;
46
+ // SPC-015-01: structured grants are caller-supplied rather than inferred from scalar frontmatter.
47
+ if (skill.capabilityGrants?.length)
48
+ out.capability_grants = skill.capabilityGrants;
46
49
  return out;
47
50
  }
48
51
  export function messageToKernelMessage(message) {
@@ -153,7 +156,6 @@ export function entropySampleFromObservation(obs) {
153
156
  return {
154
157
  turn: obs.turn ?? 0,
155
158
  score: obs.score ?? 0,
156
- scoreVersion: obs.score_version ?? 0,
157
159
  rho: obs.rho ?? 0,
158
160
  repeatPressure: obs.repeat_pressure ?? 0,
159
161
  failureRate: obs.failure_rate ?? 0,
@@ -1,7 +1,31 @@
1
- import type { ToolCall, ToolSchema, StreamEvent } from "../types.js";
1
+ import type { ToolCall, ToolSchema, StreamEvent, ToolOutputBlock } from "../types.js";
2
2
  import type { RegisteredTool } from "../tools/index.js";
3
3
  import type { ExecutionPlane, RunContext } from "./execution-plane.js";
4
4
  import type { CredentialVault } from "./credential-vault.js";
5
+ /** Raw MCP `tools/call` response `content` block shapes we accept (text/image/audio). */
6
+ export interface McpContentBlock {
7
+ type: string;
8
+ text?: string;
9
+ data?: string;
10
+ mimeType?: string;
11
+ }
12
+ /**
13
+ * spc_012-N-02: pure conversion, unit-testable without spawning a subprocess. Prior behavior
14
+ * silently dropped every non-`"text"` block (INV-012-01 violation — an MCP tool returning a
15
+ * screenshot lost the image with no trace). `output` stays the text projection for backward
16
+ * available as the text projection — when non-text blocks exist the projection
17
+ * carries an explicit `[modality]` placeholder, so text-only providers (spc_012-N-04) degrade visibly instead
18
+ * of swallowing the block. `contentParts` is populated (and only populated) when the response
19
+ * actually contains a non-text block, so pure-text tool calls see zero behavior change.
20
+ */
21
+ export declare function mcpResultToToolOutput(result: {
22
+ content?: McpContentBlock[];
23
+ isError?: boolean;
24
+ }): {
25
+ output: string;
26
+ isError: boolean;
27
+ contentParts?: ToolOutputBlock[];
28
+ };
5
29
  export interface McpServerConfig {
6
30
  /** Executable to run (e.g. "npx", "python3", "/usr/local/bin/my-mcp-server"). */
7
31
  command: string;
@@ -3,6 +3,41 @@ import { createInterface } from "node:readline";
3
3
  import { LocalExecutionPlane } from "./execution-plane.js";
4
4
  import { formatToolError } from "../tools/errors.js";
5
5
  import { operationAbortSignal } from "./reliability.js";
6
+ /**
7
+ * spc_012-N-02: pure conversion, unit-testable without spawning a subprocess. Prior behavior
8
+ * silently dropped every non-`"text"` block (INV-012-01 violation — an MCP tool returning a
9
+ * screenshot lost the image with no trace). `output` stays the text projection for backward
10
+ * available as the text projection — when non-text blocks exist the projection
11
+ * carries an explicit `[modality]` placeholder, so text-only providers (spc_012-N-04) degrade visibly instead
12
+ * of swallowing the block. `contentParts` is populated (and only populated) when the response
13
+ * actually contains a non-text block, so pure-text tool calls see zero behavior change.
14
+ */
15
+ export function mcpResultToToolOutput(result) {
16
+ const blocks = result.content ?? [];
17
+ const hasNonText = blocks.some(c => c.type !== "text");
18
+ if (!hasNonText) {
19
+ const text = blocks
20
+ .filter(c => c.type === "text")
21
+ .map(c => c.text ?? "")
22
+ .join("\n");
23
+ return { output: text || JSON.stringify(result), isError: result.isError ?? false };
24
+ }
25
+ const output = blocks
26
+ .map(c => (c.type === "text" ? c.text ?? "" : `[${c.type}]`))
27
+ .join("\n");
28
+ const contentParts = blocks.map(c => {
29
+ if (c.type === "text")
30
+ return { type: "text", text: c.text ?? "" };
31
+ if (c.type === "image") {
32
+ return { type: "image", source: { kind: "base64", data: c.data ?? "" }, mediaType: c.mimeType };
33
+ }
34
+ if (c.type === "audio") {
35
+ return { type: "audio", source: { kind: "base64", data: c.data ?? "" }, mediaType: c.mimeType ?? "audio/wav" };
36
+ }
37
+ return { type: "text", text: JSON.stringify(c) };
38
+ });
39
+ return { output, isError: result.isError ?? false, contentParts };
40
+ }
6
41
  class McpConnection {
7
42
  serverName;
8
43
  config;
@@ -106,11 +141,7 @@ class McpConnection {
106
141
  try {
107
142
  const args = JSON.parse(call.arguments || "{}");
108
143
  const result = await this.request("tools/call", { name: call.name, arguments: args }, signal);
109
- const text = (result.content ?? [])
110
- .filter(c => c.type === "text")
111
- .map(c => c.text ?? "")
112
- .join("\n");
113
- return { output: text || JSON.stringify(result), isError: result.isError ?? false };
144
+ return mcpResultToToolOutput(result);
114
145
  }
115
146
  catch (err) {
116
147
  return { output: formatToolError(err), isError: true };
@@ -215,7 +246,14 @@ export class McpProxyPlane {
215
246
  });
216
247
  for (const settled of await Promise.all(tasks)) {
217
248
  for (const { call, result } of settled) {
218
- yield { type: "tool_result", callId: call.id, name: call.name, content: result.output, isError: result.isError };
249
+ yield {
250
+ type: "tool_result",
251
+ callId: call.id,
252
+ name: call.name,
253
+ content: result.output,
254
+ isError: result.isError,
255
+ contentParts: result.contentParts,
256
+ };
219
257
  }
220
258
  }
221
259
  }
@@ -2,7 +2,7 @@ export interface SchemaValidation {
2
2
  ok: boolean;
3
3
  errors: string[];
4
4
  }
5
- type JsonSchema = Record<string, unknown>;
5
+ export type JsonSchema = Record<string, unknown>;
6
6
  /** Validate `value` against `schema` (the supported subset). `path` is for error messages. */
7
7
  export declare function validateAgainstSchema(value: unknown, schema: JsonSchema, path?: string): SchemaValidation;
8
8
  /** The instruction appended to a node's goal so its agent produces schema-conforming JSON. */
@@ -11,4 +11,3 @@ export declare function schemaInstruction(schema: JsonSchema): string;
11
11
  export declare function schemaRetryInstruction(schema: JsonSchema, errors: string[]): string;
12
12
  /** Best-effort extraction of a JSON value from agent output (raw, fenced, or embedded). */
13
13
  export declare function extractJsonValue(text: string): unknown;
14
- export {};
@@ -7,7 +7,7 @@ export declare function assistantReplayKey(message: Pick<Message, "content" | "t
7
7
  * incompatible envelope is skipped so B re-serializes neutral context instead
8
8
  * of replaying A's protocol-specific shape.
9
9
  */
10
- export declare function isReplayCompatibleWithProvider(replay: ProviderReplay, descriptor: ProviderDescriptor | undefined): boolean;
10
+ export declare function isReplayCompatibleWithProvider(replay: Partial<ProviderReplay>, descriptor: ProviderDescriptor | undefined): boolean;
11
11
  export declare function seedProviderReplayFromEvents(provider: LLMProvider, events: Array<{
12
12
  event: SessionEvent;
13
13
  }>): void;
@@ -33,22 +33,6 @@ export function assistantReplayKey(message) {
33
33
  toolCalls,
34
34
  });
35
35
  }
36
- /**
37
- * Infer the wire protocol a stored replay envelope belongs to.
38
- *
39
- * New envelopes carry an explicit `protocol`. Legacy envelopes are inferred
40
- * from their shape: Anthropic persisted `native_blocks`, OpenAI-compatible
41
- * persisted `reasoning_content` / `reasoning_details`.
42
- */
43
- function replayProtocol(replay) {
44
- if (replay.protocol)
45
- return replay.protocol;
46
- if (replay.native_blocks?.length)
47
- return "anthropic-messages";
48
- if (replay.reasoning_content != null || replay.reasoning_details !== undefined)
49
- return "openai-chat";
50
- return undefined;
51
- }
52
36
  /**
53
37
  * A stored replay may only be seeded into a provider speaking the same wire
54
38
  * protocol. On a cross-protocol fallback (provider A -> provider B) the
@@ -56,12 +40,16 @@ function replayProtocol(replay) {
56
40
  * of replaying A's protocol-specific shape.
57
41
  */
58
42
  export function isReplayCompatibleWithProvider(replay, descriptor) {
59
- if (!descriptor)
60
- return true;
61
- const protocol = replayProtocol(replay);
62
- if (!protocol)
63
- return true;
64
- return protocol === descriptor.protocol;
43
+ assertCanonicalReplay(replay);
44
+ return !descriptor || replay.protocol === descriptor.protocol;
45
+ }
46
+ const REPLAY_KEYS = new Set(["protocol", "provider", "model", "native_blocks", "reasoning_content", "reasoning_details", "native_message", "tool_calls"]);
47
+ function assertCanonicalReplay(replay) {
48
+ for (const key of Object.keys(replay))
49
+ if (!REPLAY_KEYS.has(key))
50
+ throw new Error(`provider replay has unknown field ${key}`);
51
+ if (typeof replay.protocol !== "string" || replay.protocol.length === 0)
52
+ throw new Error("provider replay protocol is required");
65
53
  }
66
54
  export function seedProviderReplayFromEvents(provider, events) {
67
55
  if (!provider.seedProviderReplay)
@@ -72,12 +60,9 @@ export function seedProviderReplayFromEvents(provider, events) {
72
60
  continue;
73
61
  const toolCalls = event.tool_calls ?? [];
74
62
  const stored = event.provider_replay;
75
- if (stored && !isReplayCompatibleWithProvider(stored, descriptor))
63
+ if (!stored || !isReplayCompatibleWithProvider(stored, descriptor))
76
64
  continue;
77
- // Pass the message even when no replay was persisted: a provider may
78
- // reconstruct a legacy replay (e.g. Anthropic native_blocks) from the
79
- // neutral transcript. Providers that cannot reconstruct simply no-op.
80
- provider.seedProviderReplay({ content: event.content, toolCalls }, stored ?? {});
65
+ provider.seedProviderReplay({ content: event.content, toolCalls }, stored);
81
66
  }
82
67
  }
83
68
  export function peekProviderReplay(provider, content, toolCalls) {
@@ -121,7 +121,7 @@ export declare class ReactiveSession {
121
121
  * blackboard continuity comes from the (persistent) `EventStream`. Turn-policy cursor state is not
122
122
  * restored. W-N5: vehicle members (workflow envelopes, `wf-node*` children, loop iterations) share
123
123
  * the governance domain but are NOT personas — resuming them as peers would resurrect phantoms.
124
- * A legacy membership with no kind tags falls back to resuming every member.
124
+ * Only explicitly tagged peer memberships are resumable.
125
125
  */
126
126
  static resume(opts: ReactiveSessionOptions & {
127
127
  peerSpecs?: Record<string, ReactivePeerSpec>;
@@ -135,14 +135,13 @@ export class ReactiveSession {
135
135
  * blackboard continuity comes from the (persistent) `EventStream`. Turn-policy cursor state is not
136
136
  * restored. W-N5: vehicle members (workflow envelopes, `wf-node*` children, loop iterations) share
137
137
  * the governance domain but are NOT personas — resuming them as peers would resurrect phantoms.
138
- * A legacy membership with no kind tags falls back to resuming every member.
138
+ * Only explicitly tagged peer memberships are resumable.
139
139
  */
140
140
  static async resume(opts) {
141
141
  const session = new ReactiveSession(opts);
142
142
  const members = await opts.runGroup.budgetStore.members(opts.runGroup.id);
143
- const anyTagged = members.some(m => m.kind !== undefined);
144
143
  for (const member of members) {
145
- if (anyTagged && member.kind !== "peer")
144
+ if (member.kind !== "peer")
146
145
  continue;
147
146
  session.peerSpecs.set(member.sessionId, opts.peerSpecs?.[member.sessionId] ?? { role: member.role });
148
147
  }
@@ -34,7 +34,7 @@ export interface GroupMember {
34
34
  /** W-N5: what this member IS in the lineage — a `"peer"` persona (ReactiveSession.addPeer) vs a
35
35
  * `"vehicle"` session (run()/runWorkflow envelopes, workflow-node children, loop iterations).
36
36
  * `ReactiveSession.resume()` rebuilds the peer set from `"peer"` members only, so DAG-in-Peer
37
- * usage can't resurrect phantom `wf-node*` personas. Absent (legacy) = unknown. */
37
+ * usage can't resurrect phantom `wf-node*` personas. Absent means unknown. */
38
38
  kind?: "peer" | "vehicle";
39
39
  }
40
40
  export interface GroupBudgetStore {