@sema-agent/core 7.18.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 +276 -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 +8 -3
- package/dist/brain/open-responses.js +12 -4
- package/dist/brain/openai.js +7 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +6 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/ask-origin.d.ts +16 -0
- package/dist/core/ask-origin.js +8 -0
- package/dist/core/auto-mode-arming.d.ts +6 -0
- package/dist/core/auto-mode-arming.js +5 -5
- package/dist/core/auto-mode-defaults.d.ts +43 -15
- package/dist/core/auto-mode-defaults.js +4 -1
- package/dist/core/auto-mode.d.ts +74 -26
- package/dist/core/auto-mode.js +80 -11
- package/dist/core/checkpoint-store.d.ts +7 -0
- package/dist/core/checkpoint-store.js +2 -2
- 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 +89 -41
- 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 +20 -48
- package/dist/core/gate-lanes.js +17 -9
- package/dist/core/gate-outcome.d.ts +34 -6
- package/dist/core/gate-outcome.js +7 -0
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +18 -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/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- 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 +154 -11
- package/dist/core/runner/classifier-ladder.js +170 -10
- package/dist/core/runner/contracts.d.ts +46 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +7 -6
- 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-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +20 -31
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +8 -10
- 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-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +3 -2
- 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 +8 -6
- package/dist/core/runner/resume-apply.js +3 -3
- 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 +31 -16
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -25
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +41 -29
- package/dist/core/runner/synthetic-tools.js +25 -40
- 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 +160 -53
- package/dist/core/runner/turn-attachments.js +153 -51
- 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 +84 -17
- package/dist/core/sensitive-path-policy.js +37 -19
- package/dist/core/skill-listing-budget.d.ts +106 -0
- package/dist/core/skill-listing-budget.js +57 -0
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +29 -2
- package/dist/core/tool-policy.d.ts +31 -5
- package/dist/core/tool-policy.js +45 -32
- package/dist/core/tool-spec.d.ts +37 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +105 -20
- package/dist/core/write-protect.js +89 -32
- package/dist/index.d.ts +8 -7
- package/dist/index.js +6 -5
- 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 +40 -2
- package/dist/tools/fs/bash-readonly-classifier.js +376 -41
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +19 -28
- 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/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/safety.d.ts +15 -0
- package/dist/tools/fs/safety.js +7 -0
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +31 -5
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The skills-listing BUDGET SEAT as vocabulary: the two knobs a deployment may declare
|
|
3
|
+
* (`TaskSpec.skillListingBudgetFraction` / `TaskSpec.skillListingMaxDescChars`), the ONE reader that
|
|
4
|
+
* lowers them to the numbers the renderer measures against, and the disclosure announced when the
|
|
5
|
+
* budget costs a skill its description. The reader is pure; the announcer's only effect is the notice.
|
|
6
|
+
* `runner/synthetic-tools.ts` owns the renderer that consumes the numbers.
|
|
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
|
+
*
|
|
12
|
+
* Sibling of `tool-search-setting.ts` in shape and in posture: a value outside its domain REFUSES
|
|
13
|
+
* loudly rather than folding back to the default, because a knob that is silently dropped reads to the
|
|
14
|
+
* deployment as a knob that took effect.
|
|
15
|
+
*/
|
|
16
|
+
import { type EngineNotice } from "./engine-notice.js";
|
|
17
|
+
/** Fraction of the model's context window the listing may spend when the deployment declares nothing. */
|
|
18
|
+
export declare const SKILL_LISTING_BUDGET_FRACTION_DEFAULT = 0.01;
|
|
19
|
+
/** Per-skill description cap (code units, ellipsis included) when the deployment declares nothing. */
|
|
20
|
+
export declare const SKILL_LISTING_DESC_MAX_CHARS_DEFAULT = 1536;
|
|
21
|
+
/** Context window assumed for a model that declares none. */
|
|
22
|
+
export declare const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200000;
|
|
23
|
+
/**
|
|
24
|
+
* The token→byte exchange rate this budget allocates at.
|
|
25
|
+
*
|
|
26
|
+
* @contract skills.listing_budget_exchange_rate — deliberately NOT `DEFAULT_CHARS_PER_TOKEN` (3), and
|
|
27
|
+
* the difference is directional, not an inconsistency. That constant serves the compaction/context
|
|
28
|
+
* estimators, which convert bytes→tokens and must OVER-estimate the token count to stay inside a
|
|
29
|
+
* window; a low rate is the conservative choice there. This seat converts tokens→a byte ALLOWANCE,
|
|
30
|
+
* where a low rate silently SHRINKS what the deployment may spend. Two directions, two numbers, both
|
|
31
|
+
* named. At 3 the default allowance would be 6 000 bytes rather than 8 000 — a quarter of every
|
|
32
|
+
* default deployment's listing, removed by an estimator's safety margin.
|
|
33
|
+
*/
|
|
34
|
+
export declare const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
35
|
+
/** The typed refusal a budget seat outside its domain raises: a bad value never selects a smaller
|
|
36
|
+
* listing silently, and never folds back to the default budget. */
|
|
37
|
+
export declare const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
38
|
+
/** The notice code minted when the budget cost at least one skill its description or its line. */
|
|
39
|
+
export declare const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
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. */
|
|
45
|
+
export interface ResolvedSkillListingBudget {
|
|
46
|
+
/** The budget the renderer enforces over the whole block (fences, headers, markers, joiners
|
|
47
|
+
* included), in UTF-8 bytes. */
|
|
48
|
+
blockMaxBytes: number;
|
|
49
|
+
/** Per-skill description cap in code units, ellipsis included. */
|
|
50
|
+
descMaxChars: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The ONE reader of the two listing-budget seats.
|
|
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
|
+
*
|
|
61
|
+
* `skillListingBudgetFraction` must be a finite number in `(0, 1]` — zero is excluded deliberately: a
|
|
62
|
+
* zero budget is not "spend nothing", it is a listing every skill loses its description to, which the
|
|
63
|
+
* deployment expresses by declaring fewer skills. `skillListingMaxDescChars` must be a positive safe
|
|
64
|
+
* integer. Both absent ⇒ the documented defaults, which reproduce the pre-seat render byte for byte
|
|
65
|
+
* (`200 000 × 4 × 0.01` = 8 000 bytes).
|
|
66
|
+
*
|
|
67
|
+
* The window is `contextTokens ?? contextWindow`, the same physical coordinate the deferral gate
|
|
68
|
+
* measures against. A window that is absent OR unusable (non-finite, non-positive) reads as
|
|
69
|
+
* {@link SKILL_LISTING_DEFAULT_CONTEXT_TOKENS}: one rule, not a case per bad shape — a model's own
|
|
70
|
+
* declaration is judged by the model doors, and this seat must still produce a number a renderer can
|
|
71
|
+
* compare against.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveSkillListingBudget(opts: {
|
|
74
|
+
fraction?: unknown;
|
|
75
|
+
maxDescChars?: unknown;
|
|
76
|
+
model?: {
|
|
77
|
+
contextTokens?: number;
|
|
78
|
+
contextWindow?: number;
|
|
79
|
+
} | undefined;
|
|
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>;
|
|
84
|
+
/**
|
|
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`).
|
|
88
|
+
*
|
|
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.
|
|
93
|
+
*
|
|
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.
|
|
96
|
+
*/
|
|
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. */
|
|
101
|
+
total: number;
|
|
102
|
+
nameOnly: readonly string[];
|
|
103
|
+
dropped: readonly string[];
|
|
104
|
+
budget: ResolvedSkillListingBudget;
|
|
105
|
+
sessionId?: string | undefined;
|
|
106
|
+
}): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "./engine-notice.js";
|
|
2
|
+
import { describeRejectedValue } from "./tool-search-setting.js";
|
|
3
|
+
export const SKILL_LISTING_BUDGET_FRACTION_DEFAULT = 0.01;
|
|
4
|
+
export const SKILL_LISTING_DESC_MAX_CHARS_DEFAULT = 1_536;
|
|
5
|
+
export const SKILL_LISTING_DEFAULT_CONTEXT_TOKENS = 200_000;
|
|
6
|
+
export const SKILL_LISTING_BYTES_PER_TOKEN = 4;
|
|
7
|
+
export const SKILL_LISTING_BUDGET_INVALID_CODE = "config.skill_listing_budget_invalid";
|
|
8
|
+
export const SKILL_LISTING_TRUNCATED_CODE = "skills.listing_truncated";
|
|
9
|
+
export const AGENT_LISTING_TRUNCATED_CODE = "agents.listing_truncated";
|
|
10
|
+
function budgetRefusal(problem) {
|
|
11
|
+
const e = new Error(`TaskSpec.${problem}; a bad value refuses loudly, nothing falls back to the default listing budget.`);
|
|
12
|
+
e.code = SKILL_LISTING_BUDGET_INVALID_CODE;
|
|
13
|
+
return e;
|
|
14
|
+
}
|
|
15
|
+
export function resolveSkillListingBudget(opts) {
|
|
16
|
+
const { fraction, maxDescChars } = opts;
|
|
17
|
+
if (fraction !== undefined && (typeof fraction !== "number" || !Number.isFinite(fraction) || fraction <= 0 || fraction > 1)) {
|
|
18
|
+
throw budgetRefusal(`skillListingBudgetFraction: invalid value ${describeRejectedValue(fraction)} — expected a finite fraction in (0, 1]`);
|
|
19
|
+
}
|
|
20
|
+
if (maxDescChars !== undefined && (typeof maxDescChars !== "number" || !Number.isSafeInteger(maxDescChars) || maxDescChars <= 0)) {
|
|
21
|
+
throw budgetRefusal(`skillListingMaxDescChars: invalid value ${describeRejectedValue(maxDescChars)} — expected a positive integer`);
|
|
22
|
+
}
|
|
23
|
+
const declaredWindow = opts.model?.contextTokens ?? opts.model?.contextWindow;
|
|
24
|
+
const windowTokens = typeof declaredWindow === "number" && Number.isFinite(declaredWindow) && declaredWindow > 0
|
|
25
|
+
? declaredWindow
|
|
26
|
+
: SKILL_LISTING_DEFAULT_CONTEXT_TOKENS;
|
|
27
|
+
return {
|
|
28
|
+
blockMaxBytes: Math.max(1, Math.floor(windowTokens * SKILL_LISTING_BYTES_PER_TOKEN * (fraction ?? SKILL_LISTING_BUDGET_FRACTION_DEFAULT))),
|
|
29
|
+
descMaxChars: maxDescChars ?? SKILL_LISTING_DESC_MAX_CHARS_DEFAULT,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export const SKILL_LISTING_DEFAULT_BUDGET = Object.freeze(resolveSkillListingBudget({}));
|
|
33
|
+
export function announceListingTruncation(onNotice, opts) {
|
|
34
|
+
const { listing, total, nameOnly, dropped, budget } = opts;
|
|
35
|
+
if (nameOnly.length === 0 && dropped.length === 0)
|
|
36
|
+
return;
|
|
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 });
|
|
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
|
@@ -935,6 +935,33 @@ export interface TaskSpec {
|
|
|
935
935
|
* end-user-authored skills are responsible for vetting them: a skill IS instructions by design.
|
|
936
936
|
*/
|
|
937
937
|
skills?: SkillSpec[];
|
|
938
|
+
/**
|
|
939
|
+
* Fraction of the model's context window the `<skills>` listing may spend. Absent ⇒ `0.01` (1%).
|
|
940
|
+
* Domain `(0, 1]`; anything else — including `0`, a string, `NaN` — REFUSES at the door with
|
|
941
|
+
* `config.skill_listing_budget_invalid` rather than folding back to the default.
|
|
942
|
+
*
|
|
943
|
+
* The budget is `(contextTokens ?? contextWindow ?? 200 000) × 4 × fraction`, in UTF-8 bytes, and the
|
|
944
|
+
* listing degrades inside it: every skill stays listed, the lower-priority ones (declaration order)
|
|
945
|
+
* lose their descriptions first. Raising this is opting in to a higher per-turn context cost.
|
|
946
|
+
*
|
|
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).
|
|
953
|
+
*
|
|
954
|
+
* ⚠️ A model declaring a window SMALLER than 200 000 tokens now gets a proportionally smaller listing
|
|
955
|
+
* budget than the flat 8 000 bytes that preceded this seat.
|
|
956
|
+
*/
|
|
957
|
+
skillListingBudgetFraction?: number;
|
|
958
|
+
/**
|
|
959
|
+
* Per-skill description cap inside the `<skills>` listing, in code units, ellipsis included. Absent ⇒
|
|
960
|
+
* `1536`. Must be a positive integer; anything else REFUSES with
|
|
961
|
+
* `config.skill_listing_budget_invalid`. A description longer than the cap is truncated to exactly
|
|
962
|
+
* this many units; lowering it is how a deployment fits more skills' descriptions inside one budget.
|
|
963
|
+
*/
|
|
964
|
+
skillListingMaxDescChars?: number;
|
|
938
965
|
/**
|
|
939
966
|
* design/129 — lifetime scope for BACKGROUND children (Agent/Fork `run_in_background`). Default `"task"`:
|
|
940
967
|
* the parent runTask's terminal reaps them (design/115 — no orphans burning tokens; turn-end killed
|
|
@@ -1225,8 +1252,8 @@ export interface TaskSpec {
|
|
|
1225
1252
|
* back to a transcript probe for older checkpoints — never replayed when already delivered.
|
|
1226
1253
|
* - `skillsListing` ([c209-C], DEFAULT-ON, mechanism-family sibling of `agentListing` — no CC
|
|
1227
1254
|
* delta anchor, registered in cc-divergences): the FIRST user turn carries the full `<skills>`
|
|
1228
|
-
* disclosure block (the exact render that used to sit in the stable system prompt —
|
|
1229
|
-
* 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
|
|
1230
1257
|
* name-only overflow); boundaries announce only skill-set drift. Resume seeding mirrors
|
|
1231
1258
|
* `agentListing`.
|
|
1232
1259
|
* - `mcpInstructions` (G1 续批 — CC `mcp_instructions_delta` parity): the INITIAL server
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AskOrigin } from "./ask-origin.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type Settlement } from "./gate-outcome.js";
|
|
3
3
|
/** A tool call presented to a policy before it executes. */
|
|
4
4
|
/** design/388 B6 — the per-call face (see {@link ToolCallRequest.face}). */
|
|
5
5
|
export interface ToolCallFace {
|
|
@@ -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;
|
|
@@ -310,6 +321,11 @@ export type PermissionResult = {
|
|
|
310
321
|
* in the one direction the stamp cannot reach (a caller composing its own ask) the claim is
|
|
311
322
|
* tightening-only: it can make an ask outlive a rule, never turn one into an allow. */
|
|
312
323
|
probeMandated?: true;
|
|
324
|
+
/** The probe named a directory that would clear this ask
|
|
325
|
+
* ({@link import("./types.js").ReversibilityVerdict.readRootCandidate}). Display metadata like the two
|
|
326
|
+
* `probe*` above; ENGINE-STAMPED and VALIDATED at the tighten, and deliberately NOT merged onto an ask
|
|
327
|
+
* another layer raised, which a grant leaves standing. */
|
|
328
|
+
probeReadRootCandidate?: import("./types.js").ReadRootGrantCandidate;
|
|
313
329
|
/** An EXPLICIT `ask` permission rule matched this call (design/127 DSL — the CC `alwaysAskRules`
|
|
314
330
|
* shape); carries the matched rule's text. Present ⇔ a rule someone WROTE says "ask about this",
|
|
315
331
|
* never for a `defaultAction:"ask"` fallback (an unmatched call is default-closed posture, not a
|
|
@@ -1013,6 +1029,15 @@ export interface AskRequest {
|
|
|
1013
1029
|
* structure: render the entries as data, never re-derive structure by splitting or joining them.
|
|
1014
1030
|
* The durable park route carries the same value as `RiskDescriptor.probeCause`. */
|
|
1015
1031
|
readonly probeCause?: import("./checkpoint-store.js").ProbeCause;
|
|
1032
|
+
/**
|
|
1033
|
+
* The ask-facing face of {@link import("./types.js").ReversibilityVerdict.readRootCandidate}, which owns
|
|
1034
|
+
* the meaning, the absence reading and the spelling contract: PRESENT ⇒ declaring this one directory
|
|
1035
|
+
* clears THIS ask, so a card can offer "add it and stop being asked" beside "yes, this once". Filled at
|
|
1036
|
+
* the synchronous MINT station by the tighten that ran the probe — the three INHERITED-lane sites present
|
|
1037
|
+
* an ancestor's decision from upstream of it and carry none, and the durable park row has NO twin: a
|
|
1038
|
+
* parked out-of-root ask renders the confirmation alone.
|
|
1039
|
+
*/
|
|
1040
|
+
readonly readRootCandidate?: import("./types.js").ReadRootGrantCandidate;
|
|
1016
1041
|
/** design/252 G-2: the RULE-PROVENANCE evidence behind this ask — the org snapshot revision and the
|
|
1017
1042
|
* matched rules' own identity keys, each present as a value or as a named absence (see
|
|
1018
1043
|
* {@link AskRuleEvidence}). Present on EVERY ask the engine mints, so a consumer never has to tell a
|
|
@@ -1121,9 +1146,10 @@ export interface AskRequest {
|
|
|
1121
1146
|
* probe declared structural — for the built-in shell probe, a read outside the directories this
|
|
1122
1147
|
* session declared. A surface must NOT point at rule-writing here — "allow rules silence the
|
|
1123
1148
|
* classifier's questions, never a mandated one", so a rule minted in answer to this line would
|
|
1124
|
-
* never take effect where it was minted.
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1149
|
+
* never take effect where it was minted. Whether a DIRECTORY grant is worth pointing at is not this
|
|
1150
|
+
* seat's to say: it is answered per ask by {@link readRootCandidate}, which is present exactly when
|
|
1151
|
+
* widening the session's read directories would clear this one and absent for every other member of
|
|
1152
|
+
* the family — so a surface reads that seat and never infers an answer from this word;
|
|
1127
1153
|
* · `"shadowed"` — a rule the person already wrote is speaking and does not clear this ask (a
|
|
1128
1154
|
* matching allow rule that the lane refuses to honor here, or their own explicit `ask` rule);
|
|
1129
1155
|
* · `"lane_cannot_speak"` — the lane is armed and this ask IS clearable, but the offer lane
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { brandPolicyAskClass } from "./ask-class.js";
|
|
4
|
+
import { decidersNote, settledByPerson } from "./gate-outcome.js";
|
|
4
5
|
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
|
|
5
6
|
import { join, normalize as normalizePath, sep } from "node:path";
|
|
6
7
|
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
|
|
@@ -18,9 +19,6 @@ const DECISION_REASON_SET = new Set(DECISION_REASONS);
|
|
|
18
19
|
function settledByNobody(kind) {
|
|
19
20
|
return { kind, who: { party: "none" }, when: Date.now() };
|
|
20
21
|
}
|
|
21
|
-
function settledByPerson(kind, approver, note) {
|
|
22
|
-
return { kind, who: { party: "person", ...(approver !== undefined ? { approver } : {}) }, when: Date.now(), ...(note !== undefined ? { note } : {}) };
|
|
23
|
-
}
|
|
24
22
|
const ENGINE_SETTLEMENTS = new WeakMap();
|
|
25
23
|
function attestEngineSettlement(d, record, call) {
|
|
26
24
|
const s = record.settlement;
|
|
@@ -28,19 +26,18 @@ function attestEngineSettlement(d, record, call) {
|
|
|
28
26
|
ENGINE_SETTLEMENTS.set(d, { settlement, origin: record.origin, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
29
27
|
return d;
|
|
30
28
|
}
|
|
31
|
-
function
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
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));
|
|
41
38
|
}
|
|
42
39
|
export function carryEngineAttestations(from, to) {
|
|
43
|
-
|
|
40
|
+
transferEngineAttestations(from, to);
|
|
44
41
|
}
|
|
45
42
|
export function carryPrecedingSettlement(preceding, to) {
|
|
46
43
|
if (ENGINE_SETTLEMENTS.get(to) !== undefined)
|
|
@@ -62,6 +59,19 @@ export function classifierDenyCauseOf(d, call) {
|
|
|
62
59
|
return undefined;
|
|
63
60
|
return v.cause;
|
|
64
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
|
+
}
|
|
65
75
|
const ASK_UNRESOLVABLE_CAUSES_SEAT = new WeakMap();
|
|
66
76
|
export function attestAskUnresolvableCause(d, cause, call) {
|
|
67
77
|
ASK_UNRESOLVABLE_CAUSES_SEAT.set(d, { cause, toolCallId: call.toolCallId, toolName: call.toolName });
|
|
@@ -75,6 +85,7 @@ export function askUnresolvableCauseOf(d, call) {
|
|
|
75
85
|
return undefined;
|
|
76
86
|
return v.cause;
|
|
77
87
|
}
|
|
88
|
+
const ENGINE_ATTESTATION_SEATS = [ENGINE_SETTLEMENTS, CLASSIFIER_DENY_CAUSES_SEAT, ASK_UNRESOLVABLE_CAUSES_SEAT, CLASSIFIER_ROUNDS_SEAT];
|
|
78
89
|
export function engineSettlementOf(d, call) {
|
|
79
90
|
if (typeof d !== "object" || d === null)
|
|
80
91
|
return undefined;
|
|
@@ -441,7 +452,8 @@ export function combinePolicies(...policies) {
|
|
|
441
452
|
let asked;
|
|
442
453
|
let current = req;
|
|
443
454
|
let rewrite;
|
|
444
|
-
|
|
455
|
+
const records = {};
|
|
456
|
+
let anyRecord = false;
|
|
445
457
|
let ruleAskText;
|
|
446
458
|
let probeMandateSeen = false;
|
|
447
459
|
let fallbackSeen;
|
|
@@ -450,23 +462,23 @@ export function combinePolicies(...policies) {
|
|
|
450
462
|
if (d.action === "deny") {
|
|
451
463
|
const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
|
|
452
464
|
if (out !== d) {
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
if (settledAllow !== undefined && ENGINE_SETTLEMENTS.get(out) === undefined) {
|
|
456
|
-
const owned = out === d ? { ...d } : out;
|
|
457
|
-
if (owned !== d)
|
|
458
|
-
transferEngineSettlement(d, owned);
|
|
459
|
-
transferEngineSettlement(settledAllow, owned);
|
|
460
|
-
return owned;
|
|
465
|
+
transferEngineAttestations(d, out);
|
|
461
466
|
}
|
|
462
|
-
|
|
467
|
+
if (!anyRecord)
|
|
468
|
+
return out;
|
|
469
|
+
const owned = out === d ? { ...d } : out;
|
|
470
|
+
transferEngineAttestations(records, owned);
|
|
471
|
+
transferEngineAttestations(d, owned);
|
|
472
|
+
return owned;
|
|
463
473
|
}
|
|
464
474
|
if (d.updatedInput !== undefined) {
|
|
465
475
|
current = { ...current, args: d.updatedInput };
|
|
466
476
|
rewrite = d;
|
|
467
477
|
}
|
|
468
|
-
if (d.action === "allow" &&
|
|
469
|
-
|
|
478
|
+
if (d.action === "allow" && hasEngineAttestation(d)) {
|
|
479
|
+
transferEngineAttestations(d, records);
|
|
480
|
+
anyRecord = true;
|
|
481
|
+
}
|
|
470
482
|
if (d.action === "ask" && (asked === undefined || (d.requiresRealApproval === true && asked.requiresRealApproval !== true))) {
|
|
471
483
|
asked = d;
|
|
472
484
|
}
|
|
@@ -486,10 +498,10 @@ export function combinePolicies(...policies) {
|
|
|
486
498
|
return merged !== undefined ? { ...withFallback, updatedInput: merged } : withFallback;
|
|
487
499
|
}
|
|
488
500
|
const allowed = rewrite ?? ALLOW;
|
|
489
|
-
if (
|
|
501
|
+
if (!anyRecord)
|
|
490
502
|
return allowed;
|
|
491
503
|
const out = { ...allowed };
|
|
492
|
-
|
|
504
|
+
transferEngineAttestations(records, out);
|
|
493
505
|
return out;
|
|
494
506
|
},
|
|
495
507
|
};
|
|
@@ -1273,7 +1285,7 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1273
1285
|
settlement: settledByNobody("approver_contract"),
|
|
1274
1286
|
};
|
|
1275
1287
|
}
|
|
1276
|
-
reasonText = typeof suppliedReason === "string"
|
|
1288
|
+
reasonText = typeof suppliedReason === "string" ? suppliedReason : undefined;
|
|
1277
1289
|
}
|
|
1278
1290
|
catch (err) {
|
|
1279
1291
|
return {
|
|
@@ -1283,15 +1295,16 @@ async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
|
|
|
1283
1295
|
settlement: settledByNobody("approver_contract"),
|
|
1284
1296
|
};
|
|
1285
1297
|
}
|
|
1298
|
+
const note = decidersNote(reasonText);
|
|
1286
1299
|
return {
|
|
1287
1300
|
action: "deny",
|
|
1288
1301
|
message: supplied === "timeout"
|
|
1289
|
-
? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${
|
|
1290
|
-
: humanRefusalMessage(req,
|
|
1302
|
+
? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${note !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", note, REVIEWER_NOTE_MAX_BODY)}` : ""}`
|
|
1303
|
+
: humanRefusalMessage(req, note),
|
|
1291
1304
|
decisionReason: "mode",
|
|
1292
1305
|
settlement: supplied === "timeout"
|
|
1293
1306
|
? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() }
|
|
1294
|
-
: settledByPerson("human_refused", attribution.approver,
|
|
1307
|
+
: settledByPerson("human_refused", attribution.approver, note),
|
|
1295
1308
|
};
|
|
1296
1309
|
}
|
|
1297
1310
|
if (suppliedEdit === undefined)
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -62,12 +62,45 @@ export interface ReversibilityVerdict {
|
|
|
62
62
|
* its own deployment a re-confirmation and can never turn an ask into an allow.
|
|
63
63
|
*
|
|
64
64
|
* The built-in shell probe sets it for exactly one family: a listed reader naming a path outside the
|
|
65
|
-
* session's allowed directories.
|
|
66
|
-
*
|
|
67
|
-
* it
|
|
68
|
-
*
|
|
65
|
+
* session's allowed directories. That demotion HAS a clearing channel — widening the declared read
|
|
66
|
+
* directories retires it — and where the probe can name the directory, {@link readRootCandidate} carries
|
|
67
|
+
* it. A mandated ask WITHOUT that member has no configuration behind it: the rest of the family (an
|
|
68
|
+
* operator `shellGate:"always"`, the tool's egress/irreversibility marks, an org demand, an out-of-root
|
|
69
|
+
* arm with no named directory) is confirm-or-refuse, and must not be worded as though a grant answers it.
|
|
69
70
|
*/
|
|
70
71
|
mandated?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The directory-scoped GRANT that would clear THIS ask, when the probe can name one: PRESENT ⇒ the only
|
|
74
|
+
* thing between this call and a plain `reversible: true` verdict is the session's declared read
|
|
75
|
+
* containment, and adding {@link ReadRootGrantCandidate.dir} to it makes the same call run with no card.
|
|
76
|
+
*
|
|
77
|
+
* ABSENT is the ordinary world and no claim that nothing would clear the ask — it covers every demotion
|
|
78
|
+
* with no directory answer (a deny row, a read the walk could not read, an unenumerable expansion, a
|
|
79
|
+
* shape refused whatever the roots say) and every probe silent on this member. Read presence, never
|
|
80
|
+
* absence. ADVISORY display metadata, never adjudication input.
|
|
81
|
+
*/
|
|
82
|
+
readRootCandidate?: ReadRootGrantCandidate;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A directory-scoped grant a surface can offer on an approval card — see
|
|
86
|
+
* {@link ReversibilityVerdict.readRootCandidate} and `AskRequest.readRootCandidate`.
|
|
87
|
+
*
|
|
88
|
+
* @contract shell.read_boundary.grant_candidate_is_the_grant — {@link dir} is the string to ADD, not a
|
|
89
|
+
* string to show: a consumer that renders a shortened or home-relative form must send this exact spelling
|
|
90
|
+
* back when the person accepts. Core mints one spelling for both errands, and the promise is over THAT
|
|
91
|
+
* spelling reaching the directories the boundary compares against: a deployment that REWRITES a declared
|
|
92
|
+
* directory — canonicalizing a symlinked one included — must rewrite the command's spelling the same way,
|
|
93
|
+
* or it will compare a rewritten root against an unrewritten operand and ask again. Where the two differ
|
|
94
|
+
* the ask was unclearable before this seat existed and still is; the offer names the directory the command
|
|
95
|
+
* spelled, which is the only one derivable from the text.
|
|
96
|
+
*/
|
|
97
|
+
export interface ReadRootGrantCandidate {
|
|
98
|
+
/** An absolute, lexically folded directory in the boundary's own spelling; never a file or a pattern. */
|
|
99
|
+
readonly dir: string;
|
|
100
|
+
/** Always the literal `true` — a shape, not a value, so the seat cannot be widened into a "maybe this
|
|
101
|
+
* helps" hint without a visible type change. It claims THIS ask: not the deny layer (judged by pattern
|
|
102
|
+
* rather than by root, and unmoved by any grant), and not another ask the same call may raise. */
|
|
103
|
+
readonly clearsThisAsk: true;
|
|
71
104
|
}
|
|
72
105
|
export interface ToolSpec<TParams extends TSchema = TSchema> {
|
|
73
106
|
/**
|
package/dist/core/types.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { AgentTool, ThinkingLevel, ToolInputValidationContext, ToolInputVer
|
|
|
13
13
|
import type { Model, StreamFn } from "../internal/llm.js";
|
|
14
14
|
export type { ModelRef, ModelRole, RoleSpec, ModelRoles, Brain } from "./model-seat.js";
|
|
15
15
|
export type { StaleToolResultOffloadOptions, ImageInput, TaskSpec, ShellGateDoctrine } from "./task-spec.js";
|
|
16
|
-
export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
|
|
16
|
+
export type { ToolEffect, ToolContentOrigin, ReversibilityVerdict, ReadRootGrantCandidate, ToolSpec, ToolReturn, NestedUsage, NestedUsageAccum, ToolExecuteContext } from "./tool-spec.js";
|
|
17
17
|
export type { BeforeWriteRequest, BeforeWriteResult, BeforeWriteHook, TrackEditRequest, TrackEditResult, TrackFileEditHook, FileEditedNotice, FileEditedHook, HandsBandOptions } from "./hands-band.js";
|
|
18
18
|
export type { AgentDefinition, SkillManifest, SkillSpec } from "./agent-definition.js";
|
|
19
19
|
export type { McpServerSpec, A2aServerSpec, McpElicitRequest, McpElicitResponse, OnElicit } from "./mcp-server-spec.js";
|