@deepstrike/sdk 0.2.44 → 0.2.46
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/runtime/execution-plane.js +7 -5
- package/dist/runtime/runner.d.ts +7 -0
- package/dist/runtime/runner.js +15 -3
- package/dist/runtime/sub-agent-orchestrator.js +13 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +80 -3
- package/dist/types/agent.d.ts +7 -0
- package/package.json +2 -2
|
@@ -124,9 +124,11 @@ export class LocalExecutionPlane {
|
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
126
|
try {
|
|
127
|
-
const
|
|
128
|
-
const originalArgsStr = JSON.stringify(
|
|
129
|
-
|
|
127
|
+
const rawArgs = JSON.parse(call.arguments || "{}");
|
|
128
|
+
const originalArgsStr = JSON.stringify(rawArgs);
|
|
129
|
+
// validation.args, not rawArgs, from here on: a oneOf/anyOf ROOT accepts a repaired probe
|
|
130
|
+
// CLONE — the original reference never sees those repairs (auto-casts, strips, defaults).
|
|
131
|
+
const validation = validateToolArguments(registered.schema.parameters, rawArgs);
|
|
130
132
|
if (validation.error)
|
|
131
133
|
return { callId: call.id, output: `invalid arguments: ${validation.error}`, isError: true };
|
|
132
134
|
if (validation.repaired) {
|
|
@@ -135,13 +137,13 @@ export class LocalExecutionPlane {
|
|
|
135
137
|
callId: call.id,
|
|
136
138
|
name: call.name,
|
|
137
139
|
originalArguments: originalArgsStr,
|
|
138
|
-
repairedArguments: JSON.stringify(args),
|
|
140
|
+
repairedArguments: JSON.stringify(validation.args),
|
|
139
141
|
};
|
|
140
142
|
}
|
|
141
143
|
// M3/G4: pass the run context (incl. `cwd`) so cwd-aware tools scope their work to the
|
|
142
144
|
// sub-agent's worktree. `RunContext` is structurally assignable to the tool's `ToolExecContext`.
|
|
143
145
|
// The per-call `audit` helper (above) layers best-effort side-effect handling on top.
|
|
144
|
-
const output = await registered.execute(args, callCtx);
|
|
146
|
+
const output = await registered.execute(validation.args, callCtx);
|
|
145
147
|
if (isAsyncIterable(output)) {
|
|
146
148
|
let combined = "";
|
|
147
149
|
const iterator = output[Symbol.asyncIterator]();
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -255,6 +255,13 @@ export interface RuntimeOptions {
|
|
|
255
255
|
* concurrency stays vehicle-scoped (spec §2.5).
|
|
256
256
|
*/
|
|
257
257
|
runGroup?: RunGroup;
|
|
258
|
+
/**
|
|
259
|
+
* Set by the SubAgentOrchestrator for host-derived child runs: the child still joins the
|
|
260
|
+
* `runGroup` (lineage) and settles its actual terminal usage into the group ledger, but reserves
|
|
261
|
+
* no budget axes — group admission governs peer vehicles only. The child's caps stay local
|
|
262
|
+
* (kernel `maxTotalTokens` policy + `resourceQuota`). Never set this for a top-level run.
|
|
263
|
+
*/
|
|
264
|
+
nestedGroupVehicle?: boolean;
|
|
258
265
|
/**
|
|
259
266
|
* Optional long-term memory policy (`set_memory_policy`). Tunes the kernel's memory subsystem
|
|
260
267
|
* (retrieval top-k, stale-warning age, write validation, memory path). Unset leaves the kernel
|
package/dist/runtime/runner.js
CHANGED
|
@@ -559,6 +559,7 @@ export class RuntimeRunner {
|
|
|
559
559
|
spec,
|
|
560
560
|
manifest,
|
|
561
561
|
sessionLog: this.opts.sessionLog,
|
|
562
|
+
toolAccess: spec.toolAccess,
|
|
562
563
|
...(this.opts.subAgentHarness ? { harness: this.opts.subAgentHarness } : {}),
|
|
563
564
|
});
|
|
564
565
|
await this.commitKernelApply(runtime, this.pendingObservations, {
|
|
@@ -1567,13 +1568,24 @@ export class RuntimeRunner {
|
|
|
1567
1568
|
startPayload.run_spec = agentRunSpecToKernel(spec);
|
|
1568
1569
|
}
|
|
1569
1570
|
// Reserve capacity before start_run. The kernel enforces only this vehicle's grant and reports
|
|
1570
|
-
// exact terminal usage against the same opaque reservation identity.
|
|
1571
|
+
// exact terminal usage against the same opaque reservation identity. A nested vehicle joins for
|
|
1572
|
+
// lineage/settlement only: it reserves no budget axes (group admission governs peer vehicles),
|
|
1573
|
+
// so the parent's held reservation cannot squeeze the child's grant to zero.
|
|
1571
1574
|
if (this.opts.runGroup) {
|
|
1572
1575
|
const g = this.opts.runGroup;
|
|
1573
|
-
groupBudgetScope = await GroupBudgetScope.open(g, { sessionId, role: this.opts.agentId, kind: "vehicle" }, this.groupBudgetRequest());
|
|
1576
|
+
groupBudgetScope = await GroupBudgetScope.open(g, { sessionId, role: this.opts.agentId, kind: "vehicle" }, this.opts.nestedGroupVehicle ? { limits: {}, requested: {} } : this.groupBudgetRequest());
|
|
1574
1577
|
this.activeGroupBudgetScope = groupBudgetScope;
|
|
1575
1578
|
}
|
|
1576
|
-
|
|
1579
|
+
try {
|
|
1580
|
+
await this.applyKernelPolicies(runtime, groupBudgetScope);
|
|
1581
|
+
}
|
|
1582
|
+
catch (err) {
|
|
1583
|
+
// Admission failure (e.g. the kernel rejecting a zero-capacity grant): release the
|
|
1584
|
+
// reservation so it cannot linger in the group ledger, then surface the error.
|
|
1585
|
+
await groupBudgetScope?.release();
|
|
1586
|
+
this.activeGroupBudgetScope = undefined;
|
|
1587
|
+
throw err;
|
|
1588
|
+
}
|
|
1577
1589
|
// Multimodal upload: seed the user's attachments (images/audio) as a history
|
|
1578
1590
|
// message before start_run pushes the "[TASK STATE]" anchor. init_task does not
|
|
1579
1591
|
// clear history, so order becomes [attachment user msg, "Proceed…"] — both land
|
|
@@ -89,6 +89,15 @@ export class SubAgentOrchestrator {
|
|
|
89
89
|
const inherit = ctx.toolAccess === "inherit";
|
|
90
90
|
const permitted = new Set(ctx.manifest.permitted_capability_ids ?? []);
|
|
91
91
|
const metaTools = inherit ? availableMetaTools(ctx.parentOpts) : deriveMetaTools(permitted, ctx.parentOpts);
|
|
92
|
+
// A "filtered" spawn with no capability grants and no meta-tools resolves to a deny-all plane —
|
|
93
|
+
// the child model sees zero tools and reports "no tools available". Warn the host (visible, not
|
|
94
|
+
// fatal) with the fix, mirroring `maybeWarnFailureShapedChunk`'s tone. Exempt workflow nodes:
|
|
95
|
+
// `!inherit && workflow-node ⇒ quarantined ⇒ intentional deny-all, not a misconfiguration.
|
|
96
|
+
if (!inherit && !ctx.isWorkflowNode && permitted.size === 0 && metaTools.size === 0) {
|
|
97
|
+
console.warn(`[deepstrike] spawned sub-agent "${ctx.spec.identity.agentId}" resolved to zero tools ` +
|
|
98
|
+
`(deny-all filter). Mount tools as capabilities and grant via spec.capabilityFilter, or pass ` +
|
|
99
|
+
`spec.toolAccess:'inherit' to run on the parent's plane. If a tool-less child is intentional, ignore this.`);
|
|
100
|
+
}
|
|
92
101
|
const basePlane = inherit
|
|
93
102
|
? ctx.parentOpts.executionPlane
|
|
94
103
|
: new FilteredExecutionPlane(ctx.parentOpts.executionPlane, permitted, metaTools);
|
|
@@ -126,6 +135,10 @@ export class SubAgentOrchestrator {
|
|
|
126
135
|
enablePlanTool: metaTools.has("update_plan") ? ctx.parentOpts.enablePlanTool : undefined,
|
|
127
136
|
// M5 v2.1: a workflow node's `start_workflow` flattens to the parent kernel (no nested pivot).
|
|
128
137
|
isWorkflowNode: ctx.isWorkflowNode,
|
|
138
|
+
// Nested vehicle: the child joins the inherited runGroup for lineage/settlement only — it
|
|
139
|
+
// must NOT re-reserve budget axes the parent already holds (that double-reserve squeezed the
|
|
140
|
+
// child's grant to 0 and the kernel stripped its first-turn tools).
|
|
141
|
+
nestedGroupVehicle: true,
|
|
129
142
|
// The child runs under ITS OWN spec, never the parent's: the spread above would otherwise
|
|
130
143
|
// leak the parent's `runSpec` (identity, capability filter — and a LoopDriver's armed
|
|
131
144
|
// `loopRound`, giving every child a phantom pace tool). A loop-node iteration carries its
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare function toolChunkText(chunk: ToolChunk): string;
|
|
|
26
26
|
export declare function validateToolArguments(schemaJson: string, args: Record<string, unknown>): {
|
|
27
27
|
error?: string;
|
|
28
28
|
repaired: boolean;
|
|
29
|
+
args: Record<string, unknown>;
|
|
29
30
|
};
|
|
30
31
|
export declare function executeTools(calls: {
|
|
31
32
|
id: string;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import { formatToolError } from "./errors.js";
|
|
2
|
+
/** Fail at registration, not as a vendor 400 at call time: every major provider (OpenAI-compat,
|
|
3
|
+
* Anthropic, Gemini) rejects a tool whose parameters root is not `type: "object"` — the wire
|
|
4
|
+
* error ("schema must be a JSON Schema of 'type: \"object\"'") surfaces far from the tool that
|
|
5
|
+
* caused it. Union roots must be wrapped: object root + flattened properties + `oneOf` sibling. */
|
|
6
|
+
function assertObjectRootSchema(name, parameters) {
|
|
7
|
+
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters) || parameters.type !== "object") {
|
|
8
|
+
throw new Error(`tool "${name}": parameters must be a JSON Schema with root type "object" `
|
|
9
|
+
+ `(got type: ${JSON.stringify(parameters?.type ?? null)}); `
|
|
10
|
+
+ `providers reject any other root — wrap union variants as an object root with a oneOf sibling`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
2
13
|
export function tool(name, description, parameters, fn) {
|
|
14
|
+
assertObjectRootSchema(name, parameters);
|
|
3
15
|
return {
|
|
4
16
|
schema: { name, description, parameters: JSON.stringify(parameters) },
|
|
5
17
|
async execute(args, ctx) { return fn(args, ctx); },
|
|
6
18
|
};
|
|
7
19
|
}
|
|
8
20
|
export function streamingTool(name, description, parameters, fn) {
|
|
21
|
+
assertObjectRootSchema(name, parameters);
|
|
9
22
|
return {
|
|
10
23
|
schema: { name, description, parameters: JSON.stringify(parameters) },
|
|
11
24
|
execute(args, ctx) { return fn(args, ctx); },
|
|
@@ -27,12 +40,14 @@ export function validateToolArguments(schemaJson, args) {
|
|
|
27
40
|
schema = JSON.parse(schemaJson);
|
|
28
41
|
}
|
|
29
42
|
catch {
|
|
30
|
-
return { error: "invalid tool schema", repaired: false };
|
|
43
|
+
return { error: "invalid tool schema", repaired: false, args };
|
|
31
44
|
}
|
|
32
45
|
const state = { repaired: false };
|
|
33
46
|
const wrapper = { root: args };
|
|
34
47
|
const error = validateValue(schema, wrapper, "root", "$", state);
|
|
35
|
-
|
|
48
|
+
// A oneOf/anyOf ROOT replaces the value with its accepted probe clone — in-place mutation of
|
|
49
|
+
// the caller's object only covers non-union roots. Callers must use the returned `args`.
|
|
50
|
+
return { error, repaired: state.repaired, args: wrapper.root };
|
|
36
51
|
}
|
|
37
52
|
function validateValue(schema, parent, key, path, state) {
|
|
38
53
|
let value = parent[key];
|
|
@@ -188,12 +203,72 @@ function validateValue(schema, parent, key, path, state) {
|
|
|
188
203
|
if (typeof value !== "boolean")
|
|
189
204
|
return `${path} must be boolean`;
|
|
190
205
|
}
|
|
206
|
+
else if (expectedType === "null") {
|
|
207
|
+
if (value !== null)
|
|
208
|
+
return `${path} must be null`;
|
|
209
|
+
}
|
|
191
210
|
}
|
|
192
211
|
else if (path === "$" && (!value || typeof value !== "object" || Array.isArray(value))) {
|
|
193
212
|
return `${path} must be object`;
|
|
194
213
|
}
|
|
195
214
|
if (Array.isArray(schema.enum) && !schema.enum.includes(value))
|
|
196
215
|
return `${path} must be one of enum values`;
|
|
216
|
+
// `const` is THE discriminator convention for oneOf variants (kind: {const: "edit"}). Without
|
|
217
|
+
// it, union branches match on required+type alone and the WRONG branch can win — then its
|
|
218
|
+
// allow-list strips keys the right branch declared.
|
|
219
|
+
if ("const" in schema) {
|
|
220
|
+
const want = schema.const;
|
|
221
|
+
const matches = want !== null && typeof want === "object"
|
|
222
|
+
? JSON.stringify(value) === JSON.stringify(want)
|
|
223
|
+
: value === want;
|
|
224
|
+
if (!matches)
|
|
225
|
+
return `${path} must equal the const value ${JSON.stringify(want)}`;
|
|
226
|
+
}
|
|
227
|
+
// Constraint keywords, checked per the value's actual type (JSON Schema semantics: string
|
|
228
|
+
// constraints ignore non-strings, etc.). Keywords outside this set (allOf, multipleOf,
|
|
229
|
+
// uniqueItems, format, if/then/else, …) are ignored, not rejected.
|
|
230
|
+
if (typeof value === "string") {
|
|
231
|
+
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
232
|
+
return `${path} must be at least ${schema.minLength} characters`;
|
|
233
|
+
}
|
|
234
|
+
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
235
|
+
return `${path} must be at most ${schema.maxLength} characters`;
|
|
236
|
+
}
|
|
237
|
+
if (typeof schema.pattern === "string") {
|
|
238
|
+
let re;
|
|
239
|
+
try {
|
|
240
|
+
re = new RegExp(schema.pattern);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
re = undefined;
|
|
244
|
+
} // author-side bad regex: skip, never fail the call
|
|
245
|
+
if (re && !re.test(value))
|
|
246
|
+
return `${path} must match pattern ${schema.pattern}`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (typeof value === "number") {
|
|
250
|
+
if (typeof schema.minimum === "number" && value < schema.minimum)
|
|
251
|
+
return `${path} must be >= ${schema.minimum}`;
|
|
252
|
+
if (typeof schema.maximum === "number" && value > schema.maximum)
|
|
253
|
+
return `${path} must be <= ${schema.maximum}`;
|
|
254
|
+
if (typeof schema.exclusiveMinimum === "number" && value <= schema.exclusiveMinimum)
|
|
255
|
+
return `${path} must be > ${schema.exclusiveMinimum}`;
|
|
256
|
+
if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum)
|
|
257
|
+
return `${path} must be < ${schema.exclusiveMaximum}`;
|
|
258
|
+
}
|
|
259
|
+
else if (Array.isArray(value)) {
|
|
260
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems)
|
|
261
|
+
return `${path} must have at least ${schema.minItems} items`;
|
|
262
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems)
|
|
263
|
+
return `${path} must have at most ${schema.maxItems} items`;
|
|
264
|
+
}
|
|
265
|
+
// `not`: probe on a clone so a matching (= rejected) subschema's repairs never leak out.
|
|
266
|
+
if (schema.not && typeof schema.not === "object" && !Array.isArray(schema.not)) {
|
|
267
|
+
const probe = { v: structuredClone(value) };
|
|
268
|
+
if (!validateValue(schema.not, probe, "v", path, { repaired: false })) {
|
|
269
|
+
return `${path} must not match the disallowed shape`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
197
272
|
return undefined;
|
|
198
273
|
}
|
|
199
274
|
export async function executeTools(calls, registry) {
|
|
@@ -206,7 +281,9 @@ export async function executeTools(calls, registry) {
|
|
|
206
281
|
const validation = validateToolArguments(t.schema.parameters, args);
|
|
207
282
|
if (validation.error)
|
|
208
283
|
return { callId: c.id, output: `invalid arguments: ${validation.error}`, isError: true };
|
|
209
|
-
|
|
284
|
+
// validation.args, not args: a oneOf/anyOf ROOT accepts a repaired probe CLONE — the
|
|
285
|
+
// original reference never sees those repairs (auto-casts, strips, defaults).
|
|
286
|
+
const output = await t.execute(validation.args);
|
|
210
287
|
if (isAsyncIterable(output)) {
|
|
211
288
|
let combined = "";
|
|
212
289
|
for await (const chunk of output)
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface AgentRunSpec {
|
|
|
52
52
|
/** O3: per-child wall-clock cap in milliseconds (sets the child runner's `timeoutMs`; falls back to
|
|
53
53
|
* the parent's). A hung child terminates `timeout` instead of stalling the parent indefinitely. */
|
|
54
54
|
maxWallMs?: number;
|
|
55
|
+
/** Tool surface for a spawned sub-agent. Host-side only (like `modelHint`) — NOT sent to the kernel
|
|
56
|
+
* (`agentRunSpecToKernel` maps fields explicitly and omits it). Default `"filtered"` keeps the spawn
|
|
57
|
+
* path's deny-all-safe default: the child is filtered to its manifest grants, and a grant-less spawn
|
|
58
|
+
* resolves to zero tools. `"inherit"` runs the child on the parent's execution plane with the
|
|
59
|
+
* parent's meta-tool availability (same mechanism trusted workflow nodes use) — the child's surface
|
|
60
|
+
* is a subset of the parent's, never a privilege escalation. */
|
|
61
|
+
toolAccess?: "inherit" | "filtered";
|
|
55
62
|
}
|
|
56
63
|
/** Kernel process-table observation (Phase 3 canonical spawn signal). */
|
|
57
64
|
export interface AgentProcessChangedObservation {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.46",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
74
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
75
|
-
"@deepstrike/core": "0.2.
|
|
75
|
+
"@deepstrike/core": "0.2.46",
|
|
76
76
|
"@google/generative-ai": "^0.24.1",
|
|
77
77
|
"openai": "^5.23.2"
|
|
78
78
|
},
|