@sema-agent/core 7.19.0 → 7.20.0
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/CHANGELOG.md +183 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +3 -1
- package/dist/brain/open-responses.js +4 -2
- package/dist/brain/openai.js +3 -1
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +4 -0
- package/dist/core/auto-mode.d.ts +37 -18
- package/dist/core/auto-mode.js +1 -1
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +67 -49
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +16 -48
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +9 -6
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +12 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +5 -0
- package/dist/core/runner/classifier-ladder.js +13 -2
- package/dist/core/runner/contracts.d.ts +44 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +5 -4
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +8 -0
- package/dist/core/runner/prepare-gate-stations.js +6 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +7 -5
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +28 -19
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -33
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +13 -39
- package/dist/core/runner/synthetic-tools.js +15 -59
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +125 -49
- package/dist/core/runner/turn-attachments.js +124 -66
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +53 -27
- package/dist/core/sensitive-path-policy.js +16 -26
- package/dist/core/skill-listing-budget.d.ts +34 -40
- package/dist/core/skill-listing-budget.js +25 -26
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +8 -7
- package/dist/core/tool-policy.d.ts +12 -1
- package/dist/core/tool-policy.js +39 -25
- package/dist/core/write-protect.d.ts +98 -18
- package/dist/core/write-protect.js +88 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
- package/dist/tools/fs/bash-readonly-classifier.js +317 -35
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +12 -25
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +19 -3
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* budget costs a skill its description. The reader is pure; the announcer's only effect is the notice.
|
|
6
6
|
* `runner/synthetic-tools.ts` owns the renderer that consumes the numbers.
|
|
7
7
|
*
|
|
8
|
+
* The listing frame is an INTACT-class turn attachment (`runner/turn-attachments.ts`
|
|
9
|
+
* `ATTACHMENT_DELIVERY_CLASS`): the delivery lane never clips it, so the number this reader produces is
|
|
10
|
+
* the number the frame is rendered to — there is no lane ceiling above it.
|
|
11
|
+
*
|
|
8
12
|
* Sibling of `tool-search-setting.ts` in shape and in posture: a value outside its domain REFUSES
|
|
9
13
|
* loudly rather than folding back to the default, because a knob that is silently dropped reads to the
|
|
10
14
|
* deployment as a knob that took effect.
|
|
@@ -28,50 +32,37 @@ export declare const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200000;
|
|
|
28
32
|
* default deployment's listing, removed by an estimator's safety margin.
|
|
29
33
|
*/
|
|
30
34
|
export declare const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
31
|
-
/**
|
|
32
|
-
* The hard ceiling on the rendered `<skills>` block, in UTF-8 bytes.
|
|
33
|
-
*
|
|
34
|
-
* @contract skills.listing_budget_ceiling — this is a STRUCTURAL property of the delivery lane, not a
|
|
35
|
-
* policy choice and not a superset optimization. The block is not part of the system prompt: it is the
|
|
36
|
-
* body of the `skills_listing` turn attachment, and every attachment bundle is capped at
|
|
37
|
-
* `ATTACHMENT_BYTE_CAP` (8 KiB) by the boundary's `capAttachments`, which CLIPS anything over. A
|
|
38
|
-
* clipped skills frame never satisfies the run loop's intact-survival commit predicate, so the
|
|
39
|
-
* announced set never advances and the frame is re-rendered and re-clipped at every subsequent
|
|
40
|
-
* boundary — permanently. The cap is therefore enforced byte-exact over the WHOLE render (fences,
|
|
41
|
-
* headers, markers, joiners included) and MUST stay ≤ `ATTACHMENT_BYTE_CAP`; the renderer's module
|
|
42
|
-
* pins that relation, since importing the attachment module here would invert the dependency.
|
|
43
|
-
*
|
|
44
|
-
* The consequence is a one-way knob: {@link resolveSkillListingBudget} clamps the declared budget to
|
|
45
|
-
* this number, so a raised fraction (or a model with a very large window) can LOWER the listing budget
|
|
46
|
-
* but can never raise it above this ceiling. When the clamp costs a description, both numbers ride the
|
|
47
|
-
* disclosure ({@link announceSkillListingTruncation}), so the deployment reads what it asked for beside
|
|
48
|
-
* what it got instead of inferring the ceiling from a short listing.
|
|
49
|
-
*/
|
|
50
|
-
export declare const SKILLS_BLOCK_MAX_BYTES = 8000;
|
|
51
35
|
/** The typed refusal a budget seat outside its domain raises: a bad value never selects a smaller
|
|
52
36
|
* listing silently, and never folds back to the default budget. */
|
|
53
37
|
export declare const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
54
38
|
/** The notice code minted when the budget cost at least one skill its description or its line. */
|
|
55
39
|
export declare const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
56
|
-
/** The
|
|
40
|
+
/** The agent-type listing's twin: minted when the budget cost at least one agent type its detail or its line. */
|
|
41
|
+
export declare const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
|
|
42
|
+
/** Which listing frame a degradation report describes. */
|
|
43
|
+
export type ListingKind = "skills" | "agents";
|
|
44
|
+
/** The lowered seat: the two numbers the renderer measures against. */
|
|
57
45
|
export interface ResolvedSkillListingBudget {
|
|
58
|
-
/** The budget the renderer enforces over the whole block,
|
|
59
|
-
*
|
|
46
|
+
/** The budget the renderer enforces over the whole block (fences, headers, markers, joiners
|
|
47
|
+
* included), in UTF-8 bytes. */
|
|
60
48
|
blockMaxBytes: number;
|
|
61
|
-
/** What the declared fraction asked for BEFORE the structural clamp. Equal to `blockMaxBytes`
|
|
62
|
-
* whenever the ceiling did not bind; strictly greater when it did. */
|
|
63
|
-
requestedBytes: number;
|
|
64
49
|
/** Per-skill description cap in code units, ellipsis included. */
|
|
65
50
|
descMaxChars: number;
|
|
66
51
|
}
|
|
67
52
|
/**
|
|
68
53
|
* The ONE reader of the two listing-budget seats.
|
|
69
54
|
*
|
|
55
|
+
* @contract skills.listing_budget_enforced_as_declared — the resolved `blockMaxBytes` is
|
|
56
|
+
* `floor(window × 4 × fraction)` with no ceiling above it: the `<skills>` frame is an intact-class
|
|
57
|
+
* attachment the delivery lane never clips, so what the deployment declares is what the renderer
|
|
58
|
+
* folds the listing into, on the initial block and on every drift frame alike. The fraction's own
|
|
59
|
+
* domain `(0, 1]` is the only upper bound (a whole window at most), and a value outside it refuses.
|
|
60
|
+
*
|
|
70
61
|
* `skillListingBudgetFraction` must be a finite number in `(0, 1]` — zero is excluded deliberately: a
|
|
71
62
|
* zero budget is not "spend nothing", it is a listing every skill loses its description to, which the
|
|
72
63
|
* deployment expresses by declaring fewer skills. `skillListingMaxDescChars` must be a positive safe
|
|
73
64
|
* integer. Both absent ⇒ the documented defaults, which reproduce the pre-seat render byte for byte
|
|
74
|
-
* (`200 000 × 4 × 0.01` = 8 000
|
|
65
|
+
* (`200 000 × 4 × 0.01` = 8 000 bytes).
|
|
75
66
|
*
|
|
76
67
|
* The window is `contextTokens ?? contextWindow`, the same physical coordinate the deferral gate
|
|
77
68
|
* measures against. A window that is absent OR unusable (non-finite, non-positive) reads as
|
|
@@ -87,23 +78,26 @@ export declare function resolveSkillListingBudget(opts: {
|
|
|
87
78
|
contextWindow?: number;
|
|
88
79
|
} | undefined;
|
|
89
80
|
}): ResolvedSkillListingBudget;
|
|
81
|
+
/** The reader's answer to "no knobs declared, no window declared" — the budget a renderer measures
|
|
82
|
+
* against when its caller hands it none. One rule: only the reader turns knobs into numbers. */
|
|
83
|
+
export declare const SKILL_LISTING_DEFAULT_BUDGET: Readonly<ResolvedSkillListingBudget>;
|
|
90
84
|
/**
|
|
91
|
-
* Announce a listing the budget degraded — delivered only when
|
|
92
|
-
* (`nameOnly`) or its line (`dropped`), so a deployment whose roster fits hears nothing.
|
|
85
|
+
* Announce a listing frame the budget degraded — delivered only when an entry actually lost its detail
|
|
86
|
+
* (`nameOnly`) or its line (`dropped`), so a deployment whose roster fits hears nothing. One announcer
|
|
87
|
+
* for both listing frames; the code names the frame (`skills.listing_truncated` / `agents.listing_truncated`).
|
|
93
88
|
*
|
|
94
|
-
* Called from the
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
89
|
+
* Called from the commit of the frame's receipt — the moment the carrying message is in the transcript —
|
|
90
|
+
* never from the config door: the door cannot see the deployment's listing switches (resolved at run
|
|
91
|
+
* start), it runs per prepared task rather than per delivered listing, and a resume leg reaches only
|
|
92
|
+
* the boundary.
|
|
98
93
|
*
|
|
99
|
-
* Audience is `operator`: the person who can raise the fraction, shorten a description or remove
|
|
100
|
-
*
|
|
101
|
-
* precisely so the structural ceiling ({@link SKILLS_BLOCK_MAX_BYTES}) is legible at the moment it
|
|
102
|
-
* costs something — the two differ only when the clamp bound.
|
|
94
|
+
* Audience is `operator`: the person who can raise the fraction, shorten a description or remove an
|
|
95
|
+
* entry runs the deployment, not the session.
|
|
103
96
|
*/
|
|
104
|
-
export declare function
|
|
105
|
-
|
|
106
|
-
|
|
97
|
+
export declare function announceListingTruncation(onNotice: ((notice: EngineNotice) => void) | undefined, opts: {
|
|
98
|
+
listing: ListingKind;
|
|
99
|
+
/** The denominator: how many entries the DELIVERED frame set out to list — the whole roster on an
|
|
100
|
+
* initial frame, the newly announced set on a drift frame. Read off that frame's own render. */
|
|
107
101
|
total: number;
|
|
108
102
|
nameOnly: readonly string[];
|
|
109
103
|
dropped: readonly string[];
|
|
@@ -4,9 +4,9 @@ export const SKILL_LISTING_BUDGET_FRACTION_DEFAULT = 0.01;
|
|
|
4
4
|
export const SKILL_LISTING_DESC_MAX_CHARS_DEFAULT = 1_536;
|
|
5
5
|
export const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200_000;
|
|
6
6
|
export const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
7
|
-
export const SKILLS_BLOCK_MAX_BYTES = 8_000;
|
|
8
7
|
export const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
9
8
|
export const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
9
|
+
export const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
|
|
10
10
|
function budgetRefusal(problem) {
|
|
11
11
|
const e = new Error(`TaskSpec.${problem}; a bad value refuses loudly, nothing falls back to the default listing budget.`);
|
|
12
12
|
e.code = SKILL_LISTING_BUDGET_INVALID_CODE;
|
|
@@ -24,35 +24,34 @@ export function resolveSkillListingBudget(opts) {
|
|
|
24
24
|
const windowTokens = typeof declaredWindow === "number" && Number.isFinite(declaredWindow) && declaredWindow > 0
|
|
25
25
|
? declaredWindow
|
|
26
26
|
: SKILL_LISTING_DEFAULT_CONTEXT_TOKENS;
|
|
27
|
-
const requestedBytes = Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT)));
|
|
28
27
|
return {
|
|
29
|
-
blockMaxBytes: Math.
|
|
30
|
-
requestedBytes,
|
|
28
|
+
blockMaxBytes: Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT))),
|
|
31
29
|
descMaxChars: maxDescChars ?? SKILL_LISTING_DESC_MAX_CHARS_DEFAULT,
|
|
32
30
|
};
|
|
33
31
|
}
|
|
34
|
-
export
|
|
35
|
-
|
|
32
|
+
export const SKILL_LISTING_DEFAULT_BUDGET = Object.freeze(resolveSkillListingBudget({}));
|
|
33
|
+
export function announceListingTruncation(onNotice, opts) {
|
|
34
|
+
const { listing, total, nameOnly, dropped, budget } = opts;
|
|
36
35
|
if (nameOnly.length === 0 && dropped.length === 0)
|
|
37
36
|
return;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
37
|
+
const skills = listing === "skills";
|
|
38
|
+
const message = `The ${skills ? "skills" : "agent-type"} listing does not fit its ${budget.blockMaxBytes}-byte budget` +
|
|
39
|
+
`: ${nameOnly.length} of ${total} are listed name-only` +
|
|
40
|
+
(dropped.length > 0 ? `, and ${dropped.length} did not fit the listing at all` : "") +
|
|
41
|
+
(skills
|
|
42
|
+
? `. The \`Skill\` tool still serves every one of them. Raise skillListingBudgetFraction, lower skillListingMaxDescChars, or declare fewer skills.`
|
|
43
|
+
: `. Raise skillListingBudgetFraction or declare fewer agent types.`);
|
|
44
|
+
const detail = {
|
|
45
|
+
budgetBytes: budget.blockMaxBytes,
|
|
46
|
+
...(skills ? { descMaxChars: budget.descMaxChars } : {}),
|
|
47
|
+
total,
|
|
48
|
+
kept: total - nameOnly.length - dropped.length,
|
|
49
|
+
nameOnly: [...nameOnly],
|
|
50
|
+
dropped: [...dropped],
|
|
51
|
+
...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
|
|
52
|
+
};
|
|
53
|
+
if (skills)
|
|
54
|
+
deliverEngineNotice(onNotice, { code: SKILL_LISTING_TRUNCATED_CODE, message, detail });
|
|
55
|
+
else
|
|
56
|
+
deliverEngineNotice(onNotice, { code: AGENT_LISTING_TRUNCATED_CODE, message, detail });
|
|
58
57
|
}
|
|
@@ -197,6 +197,19 @@ function listRelativeFiles(base, dir, prefix, out, walk) {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
function skillPhysicalRoot(skillDir, skillName, warn) {
|
|
201
|
+
try {
|
|
202
|
+
return physicalPathOf(skillDir);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
warn({
|
|
206
|
+
code: "read_failed",
|
|
207
|
+
skill: skillName,
|
|
208
|
+
detail: `the skill directory could not be resolved (${errText(err)}) — attachments skipped and no base directory is stated`,
|
|
209
|
+
});
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
200
213
|
function resolveInsideSkill(abs, rel, walk) {
|
|
201
214
|
let real;
|
|
202
215
|
let stats;
|
|
@@ -218,15 +231,7 @@ function resolveInsideSkill(abs, rel, walk) {
|
|
|
218
231
|
}
|
|
219
232
|
return { real, stats };
|
|
220
233
|
}
|
|
221
|
-
function readAttachments(skillDir, skillName, budgetBytes, warn) {
|
|
222
|
-
let realRoot;
|
|
223
|
-
try {
|
|
224
|
-
realRoot = physicalPathOf(skillDir);
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
warn({ code: "read_failed", skill: skillName, detail: `the skill directory could not be resolved (${errText(err)}) — attachments skipped` });
|
|
228
|
-
return [];
|
|
229
|
-
}
|
|
234
|
+
function readAttachments(skillDir, realRoot, skillName, budgetBytes, warn) {
|
|
230
235
|
const walk = { realRoot, skill: skillName, warn, visited: new Set([realRoot]) };
|
|
231
236
|
const relPaths = [];
|
|
232
237
|
for (const d of RESOURCE_DIRS) {
|
|
@@ -442,11 +447,13 @@ export function createSkillsFromDirectory(dir, options = {}) {
|
|
|
442
447
|
detail: `disallowed-tools names ${disallowed.map((n) => `"${n}"`).join(", ")}, but this skill produced no allowlist to subtract from (allowed-tools is absent or claims every tool) — the deny declaration is NOT enforced (express it in the task's own tool policy)`,
|
|
443
448
|
});
|
|
444
449
|
}
|
|
445
|
-
const
|
|
450
|
+
const baseDir = skillPhysicalRoot(skillDir, name, warn);
|
|
451
|
+
const files = baseDir === undefined ? [] : readAttachments(skillDir, baseDir, name, budget, warn);
|
|
446
452
|
skills.push({
|
|
447
453
|
name: declaredName,
|
|
448
454
|
description,
|
|
449
455
|
content: parsed.body,
|
|
456
|
+
...(baseDir !== undefined ? { baseDir } : {}),
|
|
450
457
|
...(manifest !== undefined ? { manifest } : {}),
|
|
451
458
|
...(files.length > 0 ? { files } : {}),
|
|
452
459
|
});
|
|
@@ -5,6 +5,7 @@ import { shutdownDebug } from "./shutdown-debug.js";
|
|
|
5
5
|
import { canAccessAgentRecord, } from "./background-agent-store.js";
|
|
6
6
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
7
7
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
8
|
+
import { stripCwdSentinel } from "../tools/fs/fs-shared.js";
|
|
8
9
|
import { registerMonitorLane, pollMonitorLane, stopMonitorLane } from "./task-registry-monitor.js";
|
|
9
10
|
import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task-registry-workflow.js";
|
|
10
11
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
@@ -46,7 +47,7 @@ function noTaskForStop(taskId, enrich) {
|
|
|
46
47
|
}
|
|
47
48
|
function bashTerminalResult(spool) {
|
|
48
49
|
const out = spool.stdout.slice(-2_000);
|
|
49
|
-
const err = spool.stderr.slice(-2_000);
|
|
50
|
+
const err = stripCwdSentinel(spool.stderr).stderr.slice(-2_000);
|
|
50
51
|
return err.length > 0 ? `${out}${out.length > 0 ? "\n" : ""}--- stderr (tail) ---\n${err}` : out;
|
|
51
52
|
}
|
|
52
53
|
async function drainBashTailBestEffort(handle) {
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -944,11 +944,12 @@ export interface TaskSpec {
|
|
|
944
944
|
* listing degrades inside it: every skill stays listed, the lower-priority ones (declaration order)
|
|
945
945
|
* lose their descriptions first. Raising this is opting in to a higher per-turn context cost.
|
|
946
946
|
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
* `
|
|
947
|
+
* Enforced as declared (`@contract skills.listing_budget_enforced_as_declared`): the listing frames are
|
|
948
|
+
* intact-class turn attachments the delivery lane never clips, so the resolved budget has no ceiling
|
|
949
|
+
* above it — a fraction that asks for more gets more (a 1 000 000-token window at the default 0.01 is
|
|
950
|
+
* 40 000 bytes). The same fraction is EACH listing attachment's budget: the agent-type roster frame
|
|
951
|
+
* folds into `window × 4 × fraction` too (every type stays listed, the last-declared lose their detail
|
|
952
|
+
* first; `agents.listing_truncated` reports it).
|
|
952
953
|
*
|
|
953
954
|
* ⚠️ A model declaring a window SMALLER than 200 000 tokens now gets a proportionally smaller listing
|
|
954
955
|
* budget than the flat 8 000 bytes that preceded this seat.
|
|
@@ -1251,8 +1252,8 @@ export interface TaskSpec {
|
|
|
1251
1252
|
* back to a transcript probe for older checkpoints — never replayed when already delivered.
|
|
1252
1253
|
* - `skillsListing` ([c209-C], DEFAULT-ON, mechanism-family sibling of `agentListing` — no CC
|
|
1253
1254
|
* delta anchor, registered in cc-divergences): the FIRST user turn carries the full `<skills>`
|
|
1254
|
-
* disclosure block (the exact render that used to sit in the stable system prompt —
|
|
1255
|
-
* byte-exact
|
|
1255
|
+
* disclosure block (the exact render that used to sit in the stable system prompt — folded
|
|
1256
|
+
* byte-exact into the listing budget, `skillListingBudgetFraction` × the window, with
|
|
1256
1257
|
* name-only overflow); boundaries announce only skill-set drift. Resume seeding mirrors
|
|
1257
1258
|
* `agentListing`.
|
|
1258
1259
|
* - `mcpInstructions` (G1 续批 — CC `mcp_instructions_delta` parity): the INITIAL server
|
|
@@ -147,7 +147,7 @@ export declare function advertisedWritableRootsSeat(seat: {
|
|
|
147
147
|
};
|
|
148
148
|
declare const DECISION_REASONS: readonly ["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only"];
|
|
149
149
|
export type DecisionReason = (typeof DECISION_REASONS)[number];
|
|
150
|
-
/** Carry every engine attestation
|
|
150
|
+
/** Carry every engine attestation from a decision onto a reconstruction of it — the
|
|
151
151
|
* seam for the engine's OWN clone sites outside this module (the inherited chain's final-form re-check carries the
|
|
152
152
|
* post-rewrite args on a spread of an ancestor's deny). Not re-exported from `src/index.ts`. */
|
|
153
153
|
export declare function carryEngineAttestations(from: object, to: object): void;
|
|
@@ -168,6 +168,17 @@ export declare function classifierDenyCauseOf(d: unknown, call: {
|
|
|
168
168
|
toolCallId: string;
|
|
169
169
|
toolName: string;
|
|
170
170
|
}): import("./gate-outcome.js").ClassifierDenyCause | undefined;
|
|
171
|
+
/** Attest that `d` is a decision a classifier station minted for `call` out of the named classification. */
|
|
172
|
+
export declare function attestClassifierRound<T extends object>(d: T, round: import("./auto-mode.js").AutoModeClassifierRound, call: {
|
|
173
|
+
toolCallId: string;
|
|
174
|
+
toolName: string;
|
|
175
|
+
}): T;
|
|
176
|
+
/** Read the attested classification off a decision FOR the named call (the gate's fold is the consumer): a
|
|
177
|
+
* binding to a different call is a replayed object and answers absence. Internal seam — not re-exported. */
|
|
178
|
+
export declare function classifierRoundOf(d: unknown, call: {
|
|
179
|
+
toolCallId: string;
|
|
180
|
+
toolName: string;
|
|
181
|
+
}): import("./auto-mode.js").AutoModeClassifierRound | undefined;
|
|
171
182
|
/** Attest that `d` is an in-fold deny of an ask nobody could resolve, for `call`, for the named cause. */
|
|
172
183
|
export declare function attestAskUnresolvableCause<T extends object>(d: T, cause: import("./ask-unresolvable-notice.js").AskUnresolvableCause, call: {
|
|
173
184
|
toolCallId: string;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -26,19 +26,18 @@ function attestEngineSettlement(d, record, call) {
|
|
|
26
26
|
ENGINE_SETTLEMENTS.set(d, { settlement, origin: record.origin, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
27
27
|
return d;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
ASK_UNRESOLVABLE_CAUSES_SEAT.set(to, u);
|
|
29
|
+
function transferEngineAttestations(from, to) {
|
|
30
|
+
for (const seat of ENGINE_ATTESTATION_SEATS) {
|
|
31
|
+
const v = seat.get(from);
|
|
32
|
+
if (v !== undefined)
|
|
33
|
+
seat.set(to, v);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function hasEngineAttestation(o) {
|
|
37
|
+
return ENGINE_ATTESTATION_SEATS.some((seat) => seat.has(o));
|
|
39
38
|
}
|
|
40
39
|
export function carryEngineAttestations(from, to) {
|
|
41
|
-
|
|
40
|
+
transferEngineAttestations(from, to);
|
|
42
41
|
}
|
|
43
42
|
export function carryPrecedingSettlement(preceding, to) {
|
|
44
43
|
if (ENGINE_SETTLEMENTS.get(to) !== undefined)
|
|
@@ -60,6 +59,19 @@ export function classifierDenyCauseOf(d, call) {
|
|
|
60
59
|
return undefined;
|
|
61
60
|
return v.cause;
|
|
62
61
|
}
|
|
62
|
+
const CLASSIFIER_ROUNDS_SEAT = new WeakMap();
|
|
63
|
+
export function attestClassifierRound(d, round, call) {
|
|
64
|
+
CLASSIFIER_ROUNDS_SEAT.set(d, { round, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
65
|
+
return d;
|
|
66
|
+
}
|
|
67
|
+
export function classifierRoundOf(d, call) {
|
|
68
|
+
if (typeof d !== "object" || d === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
const v = CLASSIFIER_ROUNDS_SEAT.get(d);
|
|
71
|
+
if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
|
|
72
|
+
return undefined;
|
|
73
|
+
return v.round;
|
|
74
|
+
}
|
|
63
75
|
const ASK_UNRESOLVABLE_CAUSES_SEAT = new WeakMap();
|
|
64
76
|
export function attestAskUnresolvableCause(d, cause, call) {
|
|
65
77
|
ASK_UNRESOLVABLE_CAUSES_SEAT.set(d, { cause, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
@@ -73,6 +85,7 @@ export function askUnresolvableCauseOf(d, call) {
|
|
|
73
85
|
return undefined;
|
|
74
86
|
return v.cause;
|
|
75
87
|
}
|
|
88
|
+
const ENGINE_ATTESTATION_SEATS = [ENGINE_SETTLEMENTS, CLASSIFIER_DENY_CAUSES_SEAT, ASK_UNRESOLVABLE_CAUSES_SEAT, CLASSIFIER_ROUNDS_SEAT];
|
|
76
89
|
export function engineSettlementOf(d, call) {
|
|
77
90
|
if (typeof d !== "object" || d === null)
|
|
78
91
|
return undefined;
|
|
@@ -439,7 +452,8 @@ export function combinePolicies(...policies) {
|
|
|
439
452
|
let asked;
|
|
440
453
|
let current = req;
|
|
441
454
|
let rewrite;
|
|
442
|
-
|
|
455
|
+
const records = {};
|
|
456
|
+
let anyRecord = false;
|
|
443
457
|
let ruleAskText;
|
|
444
458
|
let probeMandateSeen = false;
|
|
445
459
|
let fallbackSeen;
|
|
@@ -448,23 +462,23 @@ export function combinePolicies(...policies) {
|
|
|
448
462
|
if (d.action === "deny") {
|
|
449
463
|
const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
|
|
450
464
|
if (out !== d) {
|
|
451
|
-
|
|
452
|
-
}
|
|
453
|
-
if (settledAllow !== undefined && ENGINE_SETTLEMENTS.get(out) === undefined) {
|
|
454
|
-
const owned = out === d ? { ...d } : out;
|
|
455
|
-
if (owned !== d)
|
|
456
|
-
transferEngineSettlement(d, owned);
|
|
457
|
-
transferEngineSettlement(settledAllow, owned);
|
|
458
|
-
return owned;
|
|
465
|
+
transferEngineAttestations(d, out);
|
|
459
466
|
}
|
|
460
|
-
|
|
467
|
+
if (!anyRecord)
|
|
468
|
+
return out;
|
|
469
|
+
const owned = out === d ? { ...d } : out;
|
|
470
|
+
transferEngineAttestations(records, owned);
|
|
471
|
+
transferEngineAttestations(d, owned);
|
|
472
|
+
return owned;
|
|
461
473
|
}
|
|
462
474
|
if (d.updatedInput !== undefined) {
|
|
463
475
|
current = { ...current, args: d.updatedInput };
|
|
464
476
|
rewrite = d;
|
|
465
477
|
}
|
|
466
|
-
if (d.action === "allow" &&
|
|
467
|
-
|
|
478
|
+
if (d.action === "allow" && hasEngineAttestation(d)) {
|
|
479
|
+
transferEngineAttestations(d, records);
|
|
480
|
+
anyRecord = true;
|
|
481
|
+
}
|
|
468
482
|
if (d.action === "ask" && (asked === undefined || (d.requiresRealApproval === true && asked.requiresRealApproval !== true))) {
|
|
469
483
|
asked = d;
|
|
470
484
|
}
|
|
@@ -484,10 +498,10 @@ export function combinePolicies(...policies) {
|
|
|
484
498
|
return merged !== undefined ? { ...withFallback, updatedInput: merged } : withFallback;
|
|
485
499
|
}
|
|
486
500
|
const allowed = rewrite ?? ALLOW;
|
|
487
|
-
if (
|
|
501
|
+
if (!anyRecord)
|
|
488
502
|
return allowed;
|
|
489
503
|
const out = { ...allowed };
|
|
490
|
-
|
|
504
|
+
transferEngineAttestations(records, out);
|
|
491
505
|
return out;
|
|
492
506
|
},
|
|
493
507
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ExecutionEnv } from "../internal/harness-types.js";
|
|
2
|
+
import type { PermissionResult } from "./tool-policy.js";
|
|
1
3
|
import { type ToolCallLike } from "./tool-registry.js";
|
|
2
4
|
/**
|
|
3
5
|
* How one table row matches the judged path:
|
|
@@ -45,12 +47,44 @@ export declare function foldWriteProtectCase(s: string): string;
|
|
|
45
47
|
* candidate configuration with the engine's own rules.
|
|
46
48
|
*/
|
|
47
49
|
export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
+
/**
|
|
51
|
+
* The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
|
|
52
|
+
* · `clear` — no row on either view: the write is not this layer's business;
|
|
53
|
+
* · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
|
|
54
|
+
* filesystem consulted) or `"target"` (the resolved real target, the spelling being innocent);
|
|
55
|
+
* · `unresolvable` — the real target could not be resolved (an unfollowable symlink, an errored probe, a
|
|
56
|
+
* namespace spelling the tree refuses); `cause` is the canonicalizer's own sentence and the consumer
|
|
57
|
+
* REFUSES, an unjudged target never being vouched.
|
|
58
|
+
*/
|
|
59
|
+
export type WriteProtectionVerdict = {
|
|
60
|
+
readonly kind: "clear";
|
|
61
|
+
} | {
|
|
62
|
+
readonly kind: "hit";
|
|
63
|
+
readonly hit: WriteProtectedHit;
|
|
64
|
+
readonly via: "spelling" | "target";
|
|
65
|
+
} | {
|
|
66
|
+
readonly kind: "unresolvable";
|
|
67
|
+
readonly cause: string;
|
|
68
|
+
readonly unresolvedSymlink: boolean;
|
|
69
|
+
};
|
|
70
|
+
/** What one judgment knows about the call, all optional — every member has an absence reading. */
|
|
71
|
+
export interface WriteProtectionJudgeContext {
|
|
72
|
+
/** The call's LIVE tracked working directory (see {@link WriteProtectionMatcher.matchPath}). */
|
|
73
|
+
readonly cwd?: string;
|
|
74
|
+
/** The base a RELATIVE spelling resolves against on the TARGET view — the tool's own declared base
|
|
75
|
+
* ({@link pathTargetBaseOf}). Absent ⇒ `cwd`, then the compile-time `rootPath`, then the environment's
|
|
76
|
+
* own working directory: the same fallback chain the write tool itself walks. */
|
|
77
|
+
readonly base?: string;
|
|
78
|
+
/** Bounds the filesystem probes of the target view. */
|
|
79
|
+
readonly signal?: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
/** The compiled judge over one resolved table. `matchPath` is the SPELLING view alone (literal folded
|
|
82
|
+
* comparisons, no filesystem access — the admin preview face); `judge` is the whole judgment (see the
|
|
83
|
+
* module header for the two views). */
|
|
50
84
|
export interface WriteProtectionMatcher {
|
|
51
85
|
/** The resolved rows this judge was compiled from (canonical names — the disclosure face). */
|
|
52
86
|
readonly rows: readonly WriteProtectedRow[];
|
|
53
|
-
/** Judge ONE path
|
|
87
|
+
/** Judge ONE path SPELLING. Returns the first matching row (basename rows first, then segment,
|
|
54
88
|
* then segment-run — deterministic), or null.
|
|
55
89
|
*
|
|
56
90
|
* `cwd` = the call's LIVE tracked working directory, read at judgment time off the ONE ref the fs
|
|
@@ -58,33 +92,79 @@ export interface WriteProtectionMatcher {
|
|
|
58
92
|
* Absent ⇒ judged exactly as given — the historical reading, and the honest answer for a host
|
|
59
93
|
* that tracks no cwd. */
|
|
60
94
|
matchPath(path: string, cwd?: string): WriteProtectedHit | null;
|
|
95
|
+
/**
|
|
96
|
+
* Judge ONE covered write by the file the tool will write: the spelling view first (`matchPath`; a hit
|
|
97
|
+
* there is the answer and costs no I/O), then — on a miss, and only when the judge was compiled with an
|
|
98
|
+
* execution environment — the resolved real target through the shared `canonicalizeTarget`.
|
|
99
|
+
*
|
|
100
|
+
* @contract write_protection.judges_the_real_target — the always-on write-protection table and the
|
|
101
|
+
* opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
|
|
102
|
+
* (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
|
|
103
|
+
* live cwd AND its resolved real target, through one canonicalizer — and a target neither can resolve is
|
|
104
|
+
* refused, never vouched. An environment-less judge has no target view and answers by the spelling alone —
|
|
105
|
+
* the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
|
|
106
|
+
* this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
|
|
107
|
+
* tool at execute time (the module header states that window and the seat that would close it).
|
|
108
|
+
*/
|
|
109
|
+
judge(path: string, ctx?: WriteProtectionJudgeContext): Promise<WriteProtectionVerdict>;
|
|
110
|
+
}
|
|
111
|
+
/** Compile-time seats of {@link compileWriteProtection}. Every member is optional and has an absence reading. */
|
|
112
|
+
export interface WriteProtectionCompileOptions {
|
|
113
|
+
/** The engine data root for the passthrough in the module header (same seat and same meaning as
|
|
114
|
+
* `createSensitivePathPolicy`'s): omitted ⇒ the knob its consumers read (`$AGENT_DATA_DIR ?? ~/.ai-agent`). */
|
|
115
|
+
readonly dataRoot?: string;
|
|
116
|
+
/** The execution environment the hands write against — the TARGET view resolves in ITS filesystem
|
|
117
|
+
* (a remote/sandboxed task resolves in its container). Omitted ⇒ no target view: the spelling is the
|
|
118
|
+
* judgment (a host with no hands has no filesystem to resolve against). */
|
|
119
|
+
readonly env?: ExecutionEnv;
|
|
120
|
+
/** The task root — the base a `base: "root"` write tool's relative spelling resolves against, and the
|
|
121
|
+
* path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
|
|
122
|
+
* cwd, then the environment's own working directory. */
|
|
123
|
+
readonly rootPath?: string;
|
|
61
124
|
}
|
|
62
125
|
/**
|
|
63
126
|
* Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
|
|
64
127
|
* resolved table is EMPTY (`[]` replacement) — the caller then mounts no tighten at all, keeping an
|
|
65
128
|
* opted-out deployment's decision path byte-identical to a build without this layer.
|
|
66
129
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
130
|
+
* The data-root spellings are read ONCE here at compile (a judge that re-read `process.env` per call
|
|
131
|
+
* would answer two calls of one run differently); their CANONICAL keys are resolved per judgment, on the
|
|
132
|
+
* target view only, and only once a row has matched there (see {@link canonicalDataRootKeys} for why they
|
|
133
|
+
* are never cached). The passthrough is a property of the JUDGE, not of the default table — a deployment
|
|
71
134
|
* that replaces the table wholesale gets the same law over its own rows.
|
|
72
135
|
*/
|
|
73
|
-
export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?:
|
|
74
|
-
|
|
75
|
-
|
|
136
|
+
export declare function compileWriteProtection(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMatcher | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* The gate tighten's DECISION for one judgment — the words the fold writes on the pass, minted beside the
|
|
139
|
+
* verdict they read so the two cannot drift:
|
|
140
|
+
* · `hit` ⇒ `ask` (`safety`; no `requiresRealApproval` — one explicit look, clearable by an approver, a
|
|
141
|
+
* blanket `onAsk:"allow"`, the classifier or a matching persisted rule). The matched row NAME goes on the
|
|
142
|
+
* message (table names are deployment-validated vocabulary); the model-authored target path deliberately
|
|
143
|
+
* does not — `message` is the classifier's `askMessage`, and the approval card already shows the args. The
|
|
144
|
+
* spelling form's sentence is the historical one byte for byte; the target form says the target RESOLVED.
|
|
145
|
+
* · `unresolvable` ⇒ `deny` naming the cause: an unjudged target is never vouched, and an ask would be a
|
|
146
|
+
* question about a file nobody can name — the rule the canonicalizing deny set follows for the same reason.
|
|
147
|
+
* · `clear` ⇒ `undefined`: the surviving decision stands.
|
|
148
|
+
*/
|
|
149
|
+
export declare function writeProtectionTightenDecision(toolName: string, verdict: WriteProtectionVerdict): PermissionResult | undefined;
|
|
150
|
+
/**
|
|
151
|
+
* The ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`). Returns `null` SYNCHRONOUSLY when the
|
|
152
|
+
* call is not this layer's business — not a covered write, or no resolvable target string — and otherwise
|
|
153
|
+
* the judgment of {@link WriteProtectionMatcher.judge}, a promise. The split is load-bearing for the gate:
|
|
154
|
+
* a call the judge does not cover must not cost the fold a suspension point.
|
|
155
|
+
*/
|
|
156
|
+
export type WriteProtectionCheck = (toolName: string, args: unknown, face?: ToolCallLike["face"], cwd?: string, signal?: AbortSignal) => Promise<WriteProtectionVerdict> | null;
|
|
76
157
|
/**
|
|
77
158
|
* Build the ENGINE-FILLED gate input (`ToolGateInput.writeProtectionCheck`): covered tools are the
|
|
78
159
|
* path-confinable write set (Write/Edit/NotebookEdit — the shared spelling), the judged target is
|
|
79
160
|
* the SAME string the tool itself will resolve (`writeTargetPath`, the shared single source with
|
|
80
161
|
* the sensitive-path guard and the fs-write gate), and the verdict is the table judge's. A covered
|
|
81
|
-
* write with NO resolvable target returns null here — this layer is ADDITIVE friction on a named
|
|
82
|
-
* set of targets, not a containment boundary (a target-less call cannot land on a named row, and
|
|
83
|
-
*
|
|
84
|
-
*
|
|
162
|
+
* write with NO resolvable target string returns null here — this layer is ADDITIVE friction on a named
|
|
163
|
+
* set of targets, not a containment boundary (a target-less call cannot land on a named row, and the
|
|
164
|
+
* tool's own schema validation refuses it before any write); the fail-closed treatment of that case
|
|
165
|
+
* belongs to the containment gates (fs-write-gate-policy documents that split). A target string the
|
|
166
|
+
* environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
|
|
85
167
|
* Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
|
|
86
|
-
* `opts
|
|
168
|
+
* `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
|
|
87
169
|
*/
|
|
88
|
-
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?:
|
|
89
|
-
dataRoot?: string;
|
|
90
|
-
}): ((toolName: string, args: unknown, face?: ToolCallLike["face"], cwd?: string) => WriteProtectedHit | null) | undefined;
|
|
170
|
+
export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionCheck | undefined;
|