@deepstrike/sdk 0.2.47 → 0.2.48
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,3 +1,4 @@
|
|
|
1
|
+
import type { MemoryPolicy } from "../kernel.js";
|
|
1
2
|
import type { RuntimeOptions } from "../runtime/runner.js";
|
|
2
3
|
import type { NudgeRule } from "./nudge.js";
|
|
3
4
|
export interface InstructionProfile {
|
|
@@ -21,13 +22,43 @@ export declare function composeSystemPrompt(base: string | undefined, instructio
|
|
|
21
22
|
* The exact `RuntimeOptions` fields a manifest may drive. Derived via `Pick` so field names and types
|
|
22
23
|
* track `RuntimeOptions` verbatim; anything outside this set is rejected by `applyManifest`/`applyPatch`.
|
|
23
24
|
*/
|
|
24
|
-
export type HarnessRuntimePatch = Pick<RuntimeOptions, "maxTurns" | "maxTotalTokens" | "criteriaGate" | "repeatFuse" | "entropyWatch" | "knowledgeBudgetRatio" | "skillLeaseTurns">;
|
|
25
|
+
export type HarnessRuntimePatch = Pick<RuntimeOptions, "maxTurns" | "maxTotalTokens" | "criteriaGate" | "repeatFuse" | "entropyWatch" | "knowledgeBudgetRatio" | "skillLeaseTurns" | "allowedToolIds" | "stableCoreToolIds" | "enablePlanTool" | "skillFilter"> & Pick<MemoryPolicy, "retrievalTopK" | "promotionRecallThreshold">;
|
|
26
|
+
/**
|
|
27
|
+
* The promotion tier of an editable surface — the SECOND axis of the safety boundary (the whitelist is
|
|
28
|
+
* the first). Even a whitelisted surface may need a heavier gate than "typed validation passed".
|
|
29
|
+
*/
|
|
30
|
+
export type SurfaceTier = "auto" | "screened" | "human";
|
|
31
|
+
/**
|
|
32
|
+
* Map an editable surface to its promotion tier. Maintained HERE beside the whitelist so a surface can
|
|
33
|
+
* never be whitelisted without also being assigned a tier (spec V2-S3 same-place maintenance):
|
|
34
|
+
* - "auto" (Tier A): every `runtime.*` whitelist surface. Typed validation + the capability
|
|
35
|
+
* ceiling invariant + the v1 acceptance rule already guard them, so promotion is fully
|
|
36
|
+
* automatic — there is no free text and no injection surface.
|
|
37
|
+
* - "screened" (Tier B): `instructions.*` and `nudges`. Free text can smuggle instructions
|
|
38
|
+
* (persistent prompt-injection laundered through the evidence loop), so a screen runs
|
|
39
|
+
* before promotion.
|
|
40
|
+
* - "human" (Tier C): reserved for capability-WIDENING surfaces. None exist in v2 — intersection
|
|
41
|
+
* semantics make widening structurally inexpressible — but the enum value exists so a v3
|
|
42
|
+
* surface cannot be added without consciously assigning it a tier (and building the
|
|
43
|
+
* human gate). `surfaceTier` therefore never returns "human" in v2.
|
|
44
|
+
* An unknown surface / slot / runtime key THROWS (same discipline as applySurfaceEdit): a surface with
|
|
45
|
+
* no tier must never fall through to auto-promotion.
|
|
46
|
+
*/
|
|
47
|
+
export declare function surfaceTier(targetSurface: string): SurfaceTier;
|
|
25
48
|
export interface HarnessManifest {
|
|
26
49
|
manifestVersion: 1;
|
|
27
50
|
/** Parent manifest digest; `null` for a seed. */
|
|
28
51
|
parent: string | null;
|
|
29
52
|
/** Target-model identifier (per-model profile scenarios). */
|
|
30
53
|
modelProfile?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Opaque isolation key — host decides its semantics (user / tenant / agent-group). Orthogonal to
|
|
56
|
+
* `modelProfile` (never concatenate the two — that reprises the identity-scoping bug class); absent
|
|
57
|
+
* ⇒ the host treats it as `"default"`. It rides canonical JSON, so digests domain-separate by scope,
|
|
58
|
+
* but an absent scope leaves a v1-shaped manifest's digest byte-identical (canonicalJson skips
|
|
59
|
+
* undefined). Becomes a lineage directory name downstream, hence the path-safe character bound.
|
|
60
|
+
*/
|
|
61
|
+
scope?: string;
|
|
31
62
|
instructions?: InstructionProfile;
|
|
32
63
|
nudges?: NudgeRule[];
|
|
33
64
|
runtime?: HarnessRuntimePatch;
|
|
@@ -40,6 +71,10 @@ export interface HarnessManifest {
|
|
|
40
71
|
rationale?: string;
|
|
41
72
|
deltaHeldIn?: number;
|
|
42
73
|
deltaHeldOut?: number;
|
|
74
|
+
/** Promotion tier of the driving edit (V2-S3). */
|
|
75
|
+
tier?: SurfaceTier;
|
|
76
|
+
/** Injection-screen verdict — present only for a screened (Tier B) promotion (V2-S3). */
|
|
77
|
+
screenVerdict?: "pass" | "screened_out";
|
|
43
78
|
};
|
|
44
79
|
}
|
|
45
80
|
export interface HarnessPatch {
|
package/dist/harness/manifest.js
CHANGED
|
@@ -9,12 +9,22 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The whitelist is the safety boundary: governance / quota / reliability surfaces are deliberately
|
|
11
11
|
* absent, so a proposer can never rewrite them (spec design principle: conservative promotion).
|
|
12
|
+
*
|
|
13
|
+
* Tool/skill surfaces add the SECOND safety invariant (spec design principle A — the capability
|
|
14
|
+
* ceiling): `allowedToolIds`, `stableCoreToolIds`, and `skillFilter` fold onto the host baseline by
|
|
15
|
+
* INTERSECTION, never assignment. A manifest can only NARROW the tools/skills the host already
|
|
16
|
+
* exposes — never widen. Capability expansion (naming a tool the host does not expose) is therefore
|
|
17
|
+
* structurally inexpressible, and the whole security audit stays O(1): read the whitelist, check the
|
|
18
|
+
* one invariant. (`enablePlanTool` is exempt — it toggles a kernel-owned meta-tool, attention-shaping
|
|
19
|
+
* not capability-granting, so it folds by plain assignment.)
|
|
12
20
|
*/
|
|
13
21
|
import { createHash } from "node:crypto";
|
|
14
22
|
import { validateNudgeRules } from "./nudge.js";
|
|
15
23
|
const INSTRUCTION_SLOTS = ["bootstrap", "execution", "verification", "failureRecovery"];
|
|
16
24
|
/** Per-slot upper bound enforced at load and on every `applyPatch` set. */
|
|
17
25
|
const MAX_INSTRUCTION_CHARS = 4000;
|
|
26
|
+
/** A scope key becomes a directory segment; restrict it to a single path-safe token (no separators). */
|
|
27
|
+
const SCOPE_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
|
|
18
28
|
/**
|
|
19
29
|
* Compose the four instruction slots onto `base` in the fixed order base → bootstrap → execution →
|
|
20
30
|
* verification → failureRecovery, joined with `"\n\n"`, skipping empty slots. All-empty ⇒ `base`
|
|
@@ -32,6 +42,9 @@ export function composeSystemPrompt(base, instructions) {
|
|
|
32
42
|
}
|
|
33
43
|
return parts.length === 0 ? base : parts.join("\n\n");
|
|
34
44
|
}
|
|
45
|
+
const MEMORY_POLICY_PATCH_KEYS = ["retrievalTopK", "promotionRecallThreshold"];
|
|
46
|
+
/** Tool/skill surfaces whose fold is intersection-with-baseline (capability ceiling), not assignment. */
|
|
47
|
+
const INTERSECTION_PATCH_KEYS = ["allowedToolIds", "stableCoreToolIds", "skillFilter"];
|
|
35
48
|
const RUNTIME_PATCH_KEYS = [
|
|
36
49
|
"maxTurns",
|
|
37
50
|
"maxTotalTokens",
|
|
@@ -40,7 +53,52 @@ const RUNTIME_PATCH_KEYS = [
|
|
|
40
53
|
"entropyWatch",
|
|
41
54
|
"knowledgeBudgetRatio",
|
|
42
55
|
"skillLeaseTurns",
|
|
56
|
+
"allowedToolIds",
|
|
57
|
+
"stableCoreToolIds",
|
|
58
|
+
"enablePlanTool",
|
|
59
|
+
"skillFilter",
|
|
60
|
+
...MEMORY_POLICY_PATCH_KEYS,
|
|
43
61
|
];
|
|
62
|
+
/** Bounds for the id-list surfaces (allowedToolIds / stableCoreToolIds / skillFilter). */
|
|
63
|
+
const MAX_TOOL_ID_CHARS = 128;
|
|
64
|
+
const MAX_TOOL_LIST_ENTRIES = 128;
|
|
65
|
+
/**
|
|
66
|
+
* Map an editable surface to its promotion tier. Maintained HERE beside the whitelist so a surface can
|
|
67
|
+
* never be whitelisted without also being assigned a tier (spec V2-S3 same-place maintenance):
|
|
68
|
+
* - "auto" (Tier A): every `runtime.*` whitelist surface. Typed validation + the capability
|
|
69
|
+
* ceiling invariant + the v1 acceptance rule already guard them, so promotion is fully
|
|
70
|
+
* automatic — there is no free text and no injection surface.
|
|
71
|
+
* - "screened" (Tier B): `instructions.*` and `nudges`. Free text can smuggle instructions
|
|
72
|
+
* (persistent prompt-injection laundered through the evidence loop), so a screen runs
|
|
73
|
+
* before promotion.
|
|
74
|
+
* - "human" (Tier C): reserved for capability-WIDENING surfaces. None exist in v2 — intersection
|
|
75
|
+
* semantics make widening structurally inexpressible — but the enum value exists so a v3
|
|
76
|
+
* surface cannot be added without consciously assigning it a tier (and building the
|
|
77
|
+
* human gate). `surfaceTier` therefore never returns "human" in v2.
|
|
78
|
+
* An unknown surface / slot / runtime key THROWS (same discipline as applySurfaceEdit): a surface with
|
|
79
|
+
* no tier must never fall through to auto-promotion.
|
|
80
|
+
*/
|
|
81
|
+
export function surfaceTier(targetSurface) {
|
|
82
|
+
const [head, sub] = targetSurface.split(".");
|
|
83
|
+
if (head === "instructions") {
|
|
84
|
+
if (sub === undefined || !INSTRUCTION_SLOTS.includes(sub)) {
|
|
85
|
+
throw new RangeError(`unknown instruction slot: ${targetSurface}`);
|
|
86
|
+
}
|
|
87
|
+
return "screened";
|
|
88
|
+
}
|
|
89
|
+
if (head === "nudges") {
|
|
90
|
+
if (sub !== undefined)
|
|
91
|
+
throw new RangeError(`nudges surface takes no sub-path: ${targetSurface}`);
|
|
92
|
+
return "screened";
|
|
93
|
+
}
|
|
94
|
+
if (head === "runtime") {
|
|
95
|
+
if (sub === undefined || !RUNTIME_PATCH_KEYS.includes(sub)) {
|
|
96
|
+
throw new RangeError(`runtime patch key not in the editable whitelist: ${targetSurface}`);
|
|
97
|
+
}
|
|
98
|
+
return "auto";
|
|
99
|
+
}
|
|
100
|
+
throw new RangeError(`unknown surface path: ${targetSurface}`);
|
|
101
|
+
}
|
|
44
102
|
// ── Canonical JSON + digest ──────────────────────────────────────────────────
|
|
45
103
|
/** Deterministic serialization: recursive key sort, undefined-valued keys skipped, arrays ordered. */
|
|
46
104
|
function canonicalJson(value) {
|
|
@@ -93,6 +151,48 @@ function validateRuntimePatch(runtime) {
|
|
|
93
151
|
if (value !== undefined)
|
|
94
152
|
validateRuntimeValue(key, value);
|
|
95
153
|
}
|
|
154
|
+
// Same-manifest structural invariant: stable-core keeps tools exposed while a skill narrows, so it
|
|
155
|
+
// must never name a tool outside this manifest's OWN exposure ceiling (`allowedToolIds`). Checked
|
|
156
|
+
// only when both are present; either absent means the ceiling is broader (the whole registered set).
|
|
157
|
+
const allowed = runtime.allowedToolIds;
|
|
158
|
+
const stable = runtime.stableCoreToolIds;
|
|
159
|
+
if (Array.isArray(allowed) && Array.isArray(stable)) {
|
|
160
|
+
const allowedSet = new Set(allowed);
|
|
161
|
+
const outside = stable.filter(id => !allowedSet.has(id));
|
|
162
|
+
if (outside.length > 0) {
|
|
163
|
+
throw new RangeError(`runtime.stableCoreToolIds must be a subset of runtime.allowedToolIds; outside the ceiling: ${outside.join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validate an id-list surface: array of unique, non-empty strings (each ≤128 chars), ≤128 entries.
|
|
169
|
+
* `allowEmpty` is the load-bearing asymmetry. For the tool-id arrays it is FALSE: the runner reads an
|
|
170
|
+
* empty/absent `allowedToolIds` as "no gating — expose ALL registered tools", so an empty array would
|
|
171
|
+
* WIDEN exposure to everything if it reached the runner (and a zero-tool run is the v0.2.46 pathology).
|
|
172
|
+
* For `skillFilter` it is TRUE: the runner's no-gating sentinel is ONLY `undefined`, and an empty array
|
|
173
|
+
* legitimately means "no skills available" (a proposer may find skills are a distraction) — a narrowing.
|
|
174
|
+
*/
|
|
175
|
+
function validateIdList(key, value, allowEmpty) {
|
|
176
|
+
if (!Array.isArray(value))
|
|
177
|
+
throw new TypeError(`runtime.${key} must be a string[]`);
|
|
178
|
+
if (value.length > MAX_TOOL_LIST_ENTRIES) {
|
|
179
|
+
throw new RangeError(`runtime.${key} exceeds ${MAX_TOOL_LIST_ENTRIES} entries`);
|
|
180
|
+
}
|
|
181
|
+
if (!allowEmpty && value.length === 0) {
|
|
182
|
+
throw new RangeError(`runtime.${key} must be a non-empty list — an empty array is read by the runner as "no gating" (expose all registered tools), which WIDENS exposure`);
|
|
183
|
+
}
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
for (const entry of value) {
|
|
186
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
187
|
+
throw new TypeError(`runtime.${key} entries must be non-empty strings`);
|
|
188
|
+
}
|
|
189
|
+
if (entry.length > MAX_TOOL_ID_CHARS) {
|
|
190
|
+
throw new RangeError(`runtime.${key} entry exceeds ${MAX_TOOL_ID_CHARS} chars: ${entry.slice(0, 16)}…`);
|
|
191
|
+
}
|
|
192
|
+
if (seen.has(entry))
|
|
193
|
+
throw new RangeError(`runtime.${key} entries must be unique; duplicate: ${entry}`);
|
|
194
|
+
seen.add(entry);
|
|
195
|
+
}
|
|
96
196
|
}
|
|
97
197
|
/** Per-key value typing for runtime patches. An LLM proposer WILL eventually put instruction prose
|
|
98
198
|
* where a boolean belongs; rejecting it here turns a mid-run kernel `InvalidConfig` crash into a
|
|
@@ -103,6 +203,8 @@ function validateRuntimeValue(key, value) {
|
|
|
103
203
|
case "maxTurns":
|
|
104
204
|
case "maxTotalTokens":
|
|
105
205
|
case "skillLeaseTurns":
|
|
206
|
+
case "retrievalTopK":
|
|
207
|
+
case "promotionRecallThreshold":
|
|
106
208
|
if (!positiveInt(value))
|
|
107
209
|
throw new TypeError(`runtime.${key} must be a positive integer`);
|
|
108
210
|
return;
|
|
@@ -110,6 +212,17 @@ function validateRuntimeValue(key, value) {
|
|
|
110
212
|
if (typeof value !== "boolean")
|
|
111
213
|
throw new TypeError("runtime.criteriaGate must be a boolean");
|
|
112
214
|
return;
|
|
215
|
+
case "enablePlanTool":
|
|
216
|
+
if (typeof value !== "boolean")
|
|
217
|
+
throw new TypeError("runtime.enablePlanTool must be a boolean");
|
|
218
|
+
return;
|
|
219
|
+
case "allowedToolIds":
|
|
220
|
+
case "stableCoreToolIds":
|
|
221
|
+
validateIdList(key, value, /* allowEmpty */ false);
|
|
222
|
+
return;
|
|
223
|
+
case "skillFilter":
|
|
224
|
+
validateIdList(key, value, /* allowEmpty */ true);
|
|
225
|
+
return;
|
|
113
226
|
case "knowledgeBudgetRatio":
|
|
114
227
|
if (typeof value !== "number" || !(value > 0 && value <= 1)) {
|
|
115
228
|
throw new TypeError("runtime.knowledgeBudgetRatio must be a number in (0, 1]");
|
|
@@ -176,6 +289,11 @@ export function validateManifest(manifest) {
|
|
|
176
289
|
if (!Array.isArray(manifest.editableSurfaces) || manifest.editableSurfaces.some(s => typeof s !== "string")) {
|
|
177
290
|
throw new TypeError("manifest.editableSurfaces must be a string[]");
|
|
178
291
|
}
|
|
292
|
+
if (manifest.scope !== undefined) {
|
|
293
|
+
if (typeof manifest.scope !== "string" || !SCOPE_PATTERN.test(manifest.scope)) {
|
|
294
|
+
throw new TypeError("manifest.scope must be a non-empty path-safe token matching /^[A-Za-z0-9._-]{1,64}$/");
|
|
295
|
+
}
|
|
296
|
+
}
|
|
179
297
|
if (manifest.instructions !== undefined)
|
|
180
298
|
validateInstructionProfile(manifest.instructions);
|
|
181
299
|
if (manifest.nudges !== undefined)
|
|
@@ -198,12 +316,51 @@ export function applyManifest(manifest, base) {
|
|
|
198
316
|
out.nudges = manifest.nudges;
|
|
199
317
|
if (manifest.runtime !== undefined) {
|
|
200
318
|
for (const [key, value] of Object.entries(manifest.runtime)) {
|
|
201
|
-
if (value
|
|
319
|
+
if (value === undefined)
|
|
320
|
+
continue;
|
|
321
|
+
if (MEMORY_POLICY_PATCH_KEYS.includes(key)) {
|
|
322
|
+
out.memoryPolicy = { ...out.memoryPolicy, [key]: value };
|
|
323
|
+
}
|
|
324
|
+
else if (INTERSECTION_PATCH_KEYS.includes(key)) {
|
|
325
|
+
out[key] = foldIntersection(key, value, out[key]);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// enablePlanTool + numeric/boolean knobs: plain assignment.
|
|
202
329
|
out[key] = value;
|
|
330
|
+
}
|
|
203
331
|
}
|
|
204
332
|
}
|
|
205
333
|
return out;
|
|
206
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Fold one intersection surface (capability ceiling): effective = manifest ∩ host-baseline, so a
|
|
337
|
+
* manifest can only NARROW. The empty-baseline meaning is surface-specific and load-bearing:
|
|
338
|
+
*
|
|
339
|
+
* - allowedToolIds / stableCoreToolIds — the runner reads an empty OR absent baseline as
|
|
340
|
+
* "no gating = all registered tools" (the universe), so a non-array/empty baseline yields the
|
|
341
|
+
* manifest list verbatim; only a NON-EMPTY baseline is a real ceiling to intersect against. An
|
|
342
|
+
* empty intersection THROWS: a zero-tool run reprises the v0.2.46 pathology AND the runner would
|
|
343
|
+
* silently reinterpret the empty result as "no gating" (full exposure) — so we turn the candidate
|
|
344
|
+
* into a discardable error instead.
|
|
345
|
+
* - skillFilter — the runner's no-gating sentinel is ONLY `undefined`; an empty-array baseline is a
|
|
346
|
+
* genuine, maximally-tight ceiling (no skills). So ANY present array (even `[]`) is intersected,
|
|
347
|
+
* and an empty result is FINE (= no skills). This mirrors the validation asymmetry exactly.
|
|
348
|
+
*/
|
|
349
|
+
function foldIntersection(key, manifestList, baseList) {
|
|
350
|
+
const skillLike = key === "skillFilter";
|
|
351
|
+
// Is the host baseline a real constraining set? Tool ids: non-empty array only (empty == universe).
|
|
352
|
+
// skillFilter: any array (empty == the empty set).
|
|
353
|
+
const constrained = Array.isArray(baseList) && (skillLike || baseList.length > 0);
|
|
354
|
+
const effective = constrained
|
|
355
|
+
? manifestList.filter(id => baseList.includes(id)) // manifest order → deterministic
|
|
356
|
+
: manifestList;
|
|
357
|
+
if (!skillLike && effective.length === 0) {
|
|
358
|
+
throw new RangeError(`applyManifest: runtime.${key} intersection is empty — manifest [${manifestList.join(", ")}] ∩ ` +
|
|
359
|
+
`host [${(baseList ?? []).join(", ")}] names no shared tool. A zero-tool run is rejected (it ` +
|
|
360
|
+
`reprises the v0.2.46 pathology and the runner would read empty as "no gating" = full exposure).`);
|
|
361
|
+
}
|
|
362
|
+
return effective;
|
|
363
|
+
}
|
|
207
364
|
function validatePatchShape(patch) {
|
|
208
365
|
if (typeof patch !== "object" || patch === null)
|
|
209
366
|
throw new TypeError("patch must be an object");
|
package/dist/harness/public.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { VerdictFnJudge, LlmEvalJudge, HybridJudge } from "./judge.js";
|
|
|
4
4
|
export type { AttemptJudge, JudgeContext, JudgeResult, SkillCandidate } from "./judge.js";
|
|
5
5
|
export { judge } from "../runtime/eval.js";
|
|
6
6
|
export type { VerdictDetail, JudgeArgs } from "../runtime/eval.js";
|
|
7
|
-
export { composeSystemPrompt, manifestDigest, applyManifest, applyPatch, validateManifest, } from "./manifest.js";
|
|
8
|
-
export type { InstructionProfile, HarnessManifest, HarnessRuntimePatch, HarnessPatch, } from "./manifest.js";
|
|
7
|
+
export { composeSystemPrompt, manifestDigest, applyManifest, applyPatch, validateManifest, surfaceTier, } from "./manifest.js";
|
|
8
|
+
export type { InstructionProfile, HarnessManifest, HarnessRuntimePatch, HarnessPatch, SurfaceTier, } from "./manifest.js";
|
|
9
9
|
export { NudgeEngine, validateNudgeRules } from "./nudge.js";
|
|
10
10
|
export type { NudgeTrigger, NudgeRule, NudgeOutput } from "./nudge.js";
|
package/dist/harness/public.js
CHANGED
|
@@ -4,5 +4,5 @@ export { VerdictFnJudge, LlmEvalJudge, HybridJudge } from "./judge.js";
|
|
|
4
4
|
export { judge } from "../runtime/eval.js";
|
|
5
5
|
// Self-Harness H1: the harness face as data (manifest lineage + declarative event→note rules). The
|
|
6
6
|
// lab layer loads these through the compiled dist, so they live on this public barrel.
|
|
7
|
-
export { composeSystemPrompt, manifestDigest, applyManifest, applyPatch, validateManifest, } from "./manifest.js";
|
|
7
|
+
export { composeSystemPrompt, manifestDigest, applyManifest, applyPatch, validateManifest, surfaceTier, } from "./manifest.js";
|
|
8
8
|
export { NudgeEngine, validateNudgeRules } from "./nudge.js";
|
package/dist/runtime/runner.d.ts
CHANGED
|
@@ -156,6 +156,12 @@ export interface RuntimeOptions {
|
|
|
156
156
|
nudges?: NudgeRule[];
|
|
157
157
|
initialMemory?: string[];
|
|
158
158
|
skillDir?: string;
|
|
159
|
+
/** Host-layer allowlist over the `skillDir` catalog by skill NAME. When set, only scanned skills
|
|
160
|
+
* whose name is listed are fed to the kernel via `set_available_skills` (the manifest layer
|
|
161
|
+
* intersects onto this host baseline in `applyManifest`). Absent ⇒ zero behavior difference (all
|
|
162
|
+
* scanned skills fed); empty array ⇒ no skills (a legitimate narrowing — unlike an empty
|
|
163
|
+
* `allowedToolIds`, which the runner reads as "no gating"). Only takes effect when `skillDir` is set. */
|
|
164
|
+
skillFilter?: string[];
|
|
159
165
|
dreamStore?: DreamStore;
|
|
160
166
|
/** M4: advisory callback when a recalled record crosses the promotion threshold. The host/model
|
|
161
167
|
* decides whether to pin the record or promote its content into knowledge. */
|
package/dist/runtime/runner.js
CHANGED
|
@@ -1466,11 +1466,17 @@ export class RuntimeRunner {
|
|
|
1466
1466
|
if (this.opts.skillDir) {
|
|
1467
1467
|
const { scanSkillDir } = await import("../skills/loader.js");
|
|
1468
1468
|
const metas = await scanSkillDir(this.opts.skillDir);
|
|
1469
|
+
// S2 host-layer skill allowlist: keep only scanned skills named in `skillFilter` before feeding
|
|
1470
|
+
// the catalog. Absent ⇒ feed all (identical to the pre-feature message); empty ⇒ feed none. The
|
|
1471
|
+
// `set_available_skills` message is ALWAYS sent when a skillDir exists (shape preserved) — only
|
|
1472
|
+
// the list narrows; the no-skillDir path stays untouched.
|
|
1473
|
+
const filter = this.opts.skillFilter;
|
|
1474
|
+
const selected = filter === undefined ? metas : metas.filter(m => filter.includes(m.name));
|
|
1469
1475
|
// P1-B: pass the full SkillMetadata (incl. `allowedTools`) straight through — re-mapping it
|
|
1470
1476
|
// field-by-field previously dropped `allowedTools`.
|
|
1471
1477
|
await this.commitKernelApply(runtime, this.pendingObservations, {
|
|
1472
1478
|
kind: "set_available_skills",
|
|
1473
|
-
skills:
|
|
1479
|
+
skills: selected.map(m => skillMetadataToKernel(m)),
|
|
1474
1480
|
});
|
|
1475
1481
|
}
|
|
1476
1482
|
// P1-B/D: configure the stable-core tool ids (always exposed under skill gating). Empty/absent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.48",
|
|
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.48",
|
|
76
76
|
"@google/generative-ai": "^0.24.1",
|
|
77
77
|
"openai": "^5.23.2"
|
|
78
78
|
},
|