@deepstrike/sdk 0.2.18 → 0.2.19
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.
|
@@ -24,6 +24,9 @@ export function skillMetadataToKernel(skill) {
|
|
|
24
24
|
out.when_to_use = skill.whenToUse;
|
|
25
25
|
if (skill.effort !== undefined)
|
|
26
26
|
out.effort = skill.effort;
|
|
27
|
+
// P1-B: forward declared tool ids (additive; omitted when empty so existing skills' wire is unchanged).
|
|
28
|
+
if (skill.allowedTools?.length)
|
|
29
|
+
out.allowed_tools = skill.allowedTools;
|
|
27
30
|
return out;
|
|
28
31
|
}
|
|
29
32
|
export function messageToKernelMessage(message) {
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -15,6 +15,30 @@ import { LargeResultSpool } from "./large-result-spool.js";
|
|
|
15
15
|
export interface SchedulerBudget {
|
|
16
16
|
maxWallMs?: number;
|
|
17
17
|
}
|
|
18
|
+
/** P0-C tool-gating telemetry: per-LLM-turn metrics, emitted via `RuntimeOptions.onTurnMetrics`.
|
|
19
|
+
* Pure observation — no behavior change. Feeds the go/no-go analysis for epoch skill gating (P1-B):
|
|
20
|
+
* - `toolsExposed` vs `toolsCalled` quantifies over-exposure.
|
|
21
|
+
* - `activeSkill` across consecutive turns yields the skill *dwell* `D` (how long a skill stays
|
|
22
|
+
* loaded) — the break-even input that decides whether dynamic gating beats the cache-bust cost.
|
|
23
|
+
* - `cacheReadTokens` / `cacheCreationTokens` give the prompt-cache hit baseline to compare against
|
|
24
|
+
* after B/D ship. */
|
|
25
|
+
export interface TurnMetrics {
|
|
26
|
+
/** 1-based kernel turn this LLM call belongs to. */
|
|
27
|
+
turn: number;
|
|
28
|
+
/** Number of tool schemas exposed to the model this turn (base + meta, after run-profile gating). */
|
|
29
|
+
toolsExposed: number;
|
|
30
|
+
/** Number of tool calls the model emitted this turn. */
|
|
31
|
+
toolsCalled: number;
|
|
32
|
+
/** The skill loaded and in effect going into this turn (the most recent `skill` tool call's name),
|
|
33
|
+
* or undefined if none is active. Consecutive equal values measure dwell. */
|
|
34
|
+
activeSkill?: string;
|
|
35
|
+
/** Full prompt size the provider reported (uncached + cache read + cache creation). */
|
|
36
|
+
inputTokens: number;
|
|
37
|
+
/** Tokens served from the prompt cache this turn (Anthropic `cache_read_input_tokens`). */
|
|
38
|
+
cacheReadTokens: number;
|
|
39
|
+
/** Tokens written to the prompt cache this turn (Anthropic `cache_creation_input_tokens`). */
|
|
40
|
+
cacheCreationTokens: number;
|
|
41
|
+
}
|
|
18
42
|
export interface RuntimeOptions {
|
|
19
43
|
provider: LLMProvider;
|
|
20
44
|
/** M4/G5: cumulative token cap for this run (the kernel's `max_total_tokens`). A workflow node's
|
|
@@ -98,6 +122,20 @@ export interface RuntimeOptions {
|
|
|
98
122
|
}) => Promise<MilestoneCheckResult> | MilestoneCheckResult;
|
|
99
123
|
/** Passed to kernel start_run for role/isolation metadata. */
|
|
100
124
|
runSpec?: AgentRunSpec;
|
|
125
|
+
/** P0-A tool gating: a static per-run tool profile — only these tool ids (plus the
|
|
126
|
+
* skill/memory/knowledge/update_plan meta-tools) are exposed to the model each turn.
|
|
127
|
+
* Sugar that lowers to the same `capability_filter` sub-agents use; byte-stable across
|
|
128
|
+
* the run, so it never busts the prompt-cache prefix. Augments `runSpec`'s filter when
|
|
129
|
+
* both are set; synthesizes a minimal run spec when `runSpec` is absent. Omitted/empty
|
|
130
|
+
* ⇒ all registered tools exposed (no gating). */
|
|
131
|
+
allowedToolIds?: string[];
|
|
132
|
+
/** P0-C: optional per-turn metrics sink for tool-gating telemetry (see `TurnMetrics`). Pure
|
|
133
|
+
* observation; invoked once per LLM turn. Never throws into the run loop (errors are swallowed). */
|
|
134
|
+
onTurnMetrics?: (metrics: TurnMetrics) => void;
|
|
135
|
+
/** P1-B/D stable-core: tool ids that stay exposed even when an active skill narrows the toolset
|
|
136
|
+
* (read/search/bash etc.). Empty/absent ⇒ skills narrow to exactly their declared `allowed_tools`
|
|
137
|
+
* + meta-tools. Opt-in: with no skill declaring `allowed_tools`, gating never engages. */
|
|
138
|
+
stableCoreToolIds?: string[];
|
|
101
139
|
/** Loaded via load_milestone_contract before run start. */
|
|
102
140
|
milestoneContract?: MilestoneContract;
|
|
103
141
|
/** Custom sub-agent host driver; defaults to SubAgentOrchestrator. */
|
package/dist/runtime/runner.js
CHANGED
|
@@ -819,15 +819,19 @@ export class RuntimeRunner {
|
|
|
819
819
|
if (this.opts.skillDir) {
|
|
820
820
|
const { scanSkillDir } = await import("../skills/loader.js");
|
|
821
821
|
const metas = await scanSkillDir(this.opts.skillDir);
|
|
822
|
+
// P1-B: pass the full SkillMetadata (incl. `allowedTools`) straight through — re-mapping it
|
|
823
|
+
// field-by-field previously dropped `allowedTools`.
|
|
822
824
|
kernelApply(runtime, this.pendingObservations, {
|
|
823
825
|
kind: "set_available_skills",
|
|
824
|
-
skills: metas.map(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
826
|
+
skills: metas.map(m => skillMetadataToKernel(m)),
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
// P1-B/D: configure the stable-core tool ids (always exposed under skill gating). Empty/absent
|
|
830
|
+
// ⇒ skills narrow to exactly their declared tools + meta-tools.
|
|
831
|
+
if (this.opts.stableCoreToolIds?.length) {
|
|
832
|
+
kernelApply(runtime, this.pendingObservations, {
|
|
833
|
+
kind: "set_stable_core_tools",
|
|
834
|
+
tool_ids: this.opts.stableCoreToolIds,
|
|
831
835
|
});
|
|
832
836
|
}
|
|
833
837
|
if (this.opts.dreamStore && this.opts.agentId) {
|
|
@@ -876,14 +880,43 @@ export class RuntimeRunner {
|
|
|
876
880
|
kind: "preload_history",
|
|
877
881
|
messages: replayed.map(messageToKernelMessage),
|
|
878
882
|
});
|
|
883
|
+
// P1-B B3: rebuild active-skill gating after a wake by re-emitting SkillActivated for each
|
|
884
|
+
// `skill` tool call in the replayed history (active_skills is not snapshotted — graceful).
|
|
885
|
+
// The catalog (set_available_skills) was already fed above, so allowed_tools resolves.
|
|
886
|
+
for (const m of replayed) {
|
|
887
|
+
for (const tc of m.toolCalls ?? []) {
|
|
888
|
+
if (tc.name !== "skill")
|
|
889
|
+
continue;
|
|
890
|
+
try {
|
|
891
|
+
const name = JSON.parse(tc.arguments || "{}").name;
|
|
892
|
+
if (name)
|
|
893
|
+
kernelApply(runtime, this.pendingObservations, { kind: "skill_activated", name });
|
|
894
|
+
}
|
|
895
|
+
catch { /* malformed skill args — skip */ }
|
|
896
|
+
}
|
|
897
|
+
}
|
|
879
898
|
}
|
|
880
899
|
const sessionStart = Date.now();
|
|
881
900
|
const startPayload = {
|
|
882
901
|
kind: "start_run",
|
|
883
902
|
task: { goal, criteria },
|
|
884
903
|
};
|
|
885
|
-
|
|
886
|
-
|
|
904
|
+
// P0-A: lower an explicit `runSpec` and/or the `allowedToolIds` profile to the kernel's
|
|
905
|
+
// `capability_filter`. `allowedToolIds` augments an explicit spec's filter, else synthesizes
|
|
906
|
+
// a minimal top-level spec carrying just the filter (reuses the existing run_spec wire — no
|
|
907
|
+
// new ABI). Unset on both ⇒ no run_spec ⇒ no gating (铁律: no config = old behavior).
|
|
908
|
+
const allowedToolIds = this.opts.allowedToolIds;
|
|
909
|
+
const hasProfile = allowedToolIds !== undefined && allowedToolIds.length > 0;
|
|
910
|
+
if (this.opts.runSpec || hasProfile) {
|
|
911
|
+
const baseSpec = this.opts.runSpec ?? {
|
|
912
|
+
identity: { agentId: this.opts.agentId ?? "root", sessionId, isSubAgent: false },
|
|
913
|
+
role: "custom",
|
|
914
|
+
goal,
|
|
915
|
+
};
|
|
916
|
+
const spec = hasProfile
|
|
917
|
+
? { ...baseSpec, capabilityFilter: { ...baseSpec.capabilityFilter, allowedIds: allowedToolIds } }
|
|
918
|
+
: baseSpec;
|
|
919
|
+
startPayload.run_spec = agentRunSpecToKernel(spec);
|
|
887
920
|
}
|
|
888
921
|
const osProfile = assertNativeProfile(this.opts.osProfile ?? "native");
|
|
889
922
|
const attentionPolicy = this.opts.attentionPolicy ?? osProfile.attentionPolicy;
|
|
@@ -944,6 +977,9 @@ export class RuntimeRunner {
|
|
|
944
977
|
? kernelAction(runtime, this.pendingObservations, { kind: "resume" })
|
|
945
978
|
: kernelAction(runtime, this.pendingObservations, startPayload);
|
|
946
979
|
let hasAttemptedReactiveCompact = false;
|
|
980
|
+
// P0-C: the skill loaded and in effect going into the current turn (updated when the model's
|
|
981
|
+
// `skill` tool call resolves). Drives the per-turn `activeSkill` metric → dwell measurement.
|
|
982
|
+
let activeSkill;
|
|
947
983
|
while (!runtime.isTerminal()) {
|
|
948
984
|
// Page-in must run before appendObservations drains pending kernel observations.
|
|
949
985
|
if (action.kind === "execute_tool") {
|
|
@@ -985,6 +1021,8 @@ export class RuntimeRunner {
|
|
|
985
1021
|
let turnTokens = 0;
|
|
986
1022
|
let turnInputTokens = 0;
|
|
987
1023
|
let turnOutputTokens = 0;
|
|
1024
|
+
let turnCacheReadTokens = 0;
|
|
1025
|
+
let turnCacheCreationTokens = 0;
|
|
988
1026
|
let shouldRetry = false;
|
|
989
1027
|
const abortSignal = this.abortController?.signal;
|
|
990
1028
|
try {
|
|
@@ -999,6 +1037,9 @@ export class RuntimeRunner {
|
|
|
999
1037
|
turnTokens = usageEvt.totalTokens;
|
|
1000
1038
|
turnInputTokens = usageEvt.inputTokens ?? 0;
|
|
1001
1039
|
turnOutputTokens = usageEvt.outputTokens ?? 0;
|
|
1040
|
+
// P0-C: capture the prompt-cache split for the tool-gating hit-rate baseline.
|
|
1041
|
+
turnCacheReadTokens = usageEvt.cacheReadInputTokens ?? 0;
|
|
1042
|
+
turnCacheCreationTokens = usageEvt.cacheCreationInputTokens ?? 0;
|
|
1002
1043
|
continue;
|
|
1003
1044
|
}
|
|
1004
1045
|
yield evt;
|
|
@@ -1079,6 +1120,32 @@ export class RuntimeRunner {
|
|
|
1079
1120
|
toolCalls: finalToolCalls,
|
|
1080
1121
|
providerReplay,
|
|
1081
1122
|
}));
|
|
1123
|
+
// P0-C: emit per-turn tool-gating telemetry. `activeSkill` reflects the skill in effect
|
|
1124
|
+
// GOING INTO this turn; a `skill` call here only takes effect next turn, so emit first, then
|
|
1125
|
+
// advance. Wrapped so a faulty sink can never break the run (pure observation).
|
|
1126
|
+
if (this.opts.onTurnMetrics) {
|
|
1127
|
+
try {
|
|
1128
|
+
this.opts.onTurnMetrics({
|
|
1129
|
+
turn: runtime.turn(),
|
|
1130
|
+
toolsExposed: tools.length,
|
|
1131
|
+
toolsCalled: finalToolCalls.length,
|
|
1132
|
+
activeSkill,
|
|
1133
|
+
inputTokens: turnInputTokens,
|
|
1134
|
+
cacheReadTokens: turnCacheReadTokens,
|
|
1135
|
+
cacheCreationTokens: turnCacheCreationTokens,
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
catch { /* metrics must never break the run */ }
|
|
1139
|
+
}
|
|
1140
|
+
const skillCall = finalToolCalls.find(c => c.name === "skill");
|
|
1141
|
+
if (skillCall) {
|
|
1142
|
+
try {
|
|
1143
|
+
const name = JSON.parse(skillCall.arguments || "{}").name;
|
|
1144
|
+
if (name)
|
|
1145
|
+
activeSkill = name;
|
|
1146
|
+
}
|
|
1147
|
+
catch { /* malformed skill args — leave activeSkill unchanged */ }
|
|
1148
|
+
}
|
|
1082
1149
|
}
|
|
1083
1150
|
else if (action.kind === "execute_tool") {
|
|
1084
1151
|
const allCalls = action.calls;
|
|
@@ -1214,6 +1281,22 @@ export class RuntimeRunner {
|
|
|
1214
1281
|
this.pendingSpoolOutputs.set(call.id, { tool: call.name, output: result.output });
|
|
1215
1282
|
}
|
|
1216
1283
|
}
|
|
1284
|
+
// P1-B B3: a `skill` call that resolved successfully activates that skill in the kernel, so
|
|
1285
|
+
// the next `call_provider` narrows the toolset to its declared tools. Fed before `tool_results`
|
|
1286
|
+
// (which computes the next action). Errs-open: a failed/missing skill load doesn't activate.
|
|
1287
|
+
for (const call of allCalls) {
|
|
1288
|
+
if (call.name !== "skill")
|
|
1289
|
+
continue;
|
|
1290
|
+
const res = toolResults.find(r => r.callId === call.id);
|
|
1291
|
+
if (!res || res.isError)
|
|
1292
|
+
continue;
|
|
1293
|
+
try {
|
|
1294
|
+
const name = JSON.parse(call.arguments || "{}").name;
|
|
1295
|
+
if (name)
|
|
1296
|
+
kernelApply(runtime, this.pendingObservations, { kind: "skill_activated", name });
|
|
1297
|
+
}
|
|
1298
|
+
catch { /* malformed skill args — skip activation */ }
|
|
1299
|
+
}
|
|
1217
1300
|
action = kernelAction(runtime, this.pendingObservations, {
|
|
1218
1301
|
kind: "tool_results",
|
|
1219
1302
|
results: toolResults.map(toolResultToKernel),
|
package/dist/skills/loader.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export interface SkillMetadata {
|
|
|
4
4
|
whenToUse?: string;
|
|
5
5
|
effort?: number;
|
|
6
6
|
estimatedTokens?: number;
|
|
7
|
+
/** P1-B tool gating: tool ids this skill needs. When the skill is active, the kernel narrows the
|
|
8
|
+
* exposed toolset to `stable-core ∪ allowedTools`. Parsed from `allowed_tools:` frontmatter
|
|
9
|
+
* (comma-separated or `[a, b]`). Absent ⇒ the skill does not narrow (back-compat). */
|
|
10
|
+
allowedTools?: string[];
|
|
7
11
|
}
|
|
8
12
|
/** Read one skill file and return its body (frontmatter stripped). */
|
|
9
13
|
export declare function readSkillFile(skillDir: string, name: string): Promise<string | null>;
|
package/dist/skills/loader.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { readFile, readdir } from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
+
/** Parse a frontmatter tool list: `read, write` or `[read, write]` → ["read","write"]. */
|
|
4
|
+
function parseToolList(v) {
|
|
5
|
+
if (v == null || v === "")
|
|
6
|
+
return undefined;
|
|
7
|
+
const ids = String(v).trim().replace(/^\[|\]$/g, "").split(",")
|
|
8
|
+
.map(x => x.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
9
|
+
return ids.length ? ids : undefined;
|
|
10
|
+
}
|
|
3
11
|
function parseFrontmatter(content) {
|
|
4
12
|
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
5
13
|
if (!match)
|
|
@@ -38,6 +46,7 @@ export async function scanSkillDir(skillDir) {
|
|
|
38
46
|
whenToUse: meta.when_to_use ? String(meta.when_to_use) : undefined,
|
|
39
47
|
effort: meta.effort ? Number(meta.effort) : undefined,
|
|
40
48
|
estimatedTokens: meta.estimated_tokens ? Number(meta.estimated_tokens) : undefined,
|
|
49
|
+
allowedTools: parseToolList(meta.allowed_tools),
|
|
41
50
|
});
|
|
42
51
|
}
|
|
43
52
|
return results;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
23
|
-
"@deepstrike/core": "0.2.
|
|
23
|
+
"@deepstrike/core": "0.2.19",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|