@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
|
@@ -58,11 +58,6 @@ export declare const SKILL_TOOL_NAME = "Skill";
|
|
|
58
58
|
* an oversized skill is excluded from the disclosure block and the tool, with an operator-visible config error. The compaction-time retention caps are a
|
|
59
59
|
* different door, deliberately. */
|
|
60
60
|
export declare const SKILL_CONTENT_MAX_CHARS: number;
|
|
61
|
-
/** The listing's budget vocabulary lives in `core/skill-listing-budget.ts` (the seat reader that lowers
|
|
62
|
-
* the two `TaskSpec` knobs to these numbers); re-exported here because this module is where every
|
|
63
|
-
* existing consumer of the cap imports it from, and the ceiling's structural reason is a property of
|
|
64
|
-
* the delivery lane rather than of the renderer. */
|
|
65
|
-
export { SKILLS_BLOCK_MAX_BYTES } from "../skill-listing-budget.js";
|
|
66
61
|
/** Total per-invoke cap for a skill's inlined attachments (`SkillSpec.files`) — an ATTACHMENT-face budget, distinct from the (gone) body truncation door:
|
|
67
62
|
* the body is delivered untruncated at invoke time (CC parity), while attachments ride along bounded and truncation-disclosed. */
|
|
68
63
|
export declare const SKILL_ATTACHMENTS_MAX_CHARS = 50000;
|
|
@@ -139,7 +134,7 @@ export declare function buildSkillsBlock(skills: ReadonlyArray<SkillListingEntry
|
|
|
139
134
|
* [c209-C] REVERSAL: no longer composed into the stable system prompt — this is now the RENDERER for
|
|
140
135
|
* the `skills_listing` initial frame (first-user-turn reminder / boundary re-announce), so the
|
|
141
136
|
* cached prefix carries zero skill bytes (design/C §2.3; CC 209 classic ships the same listing as a
|
|
142
|
-
* first-turn reminder
|
|
137
|
+
* first-turn reminder). `budget` absent ⇒ the seat reader's no-knob answer ({@link SKILL_LISTING_DEFAULT_BUDGET}).
|
|
143
138
|
*
|
|
144
139
|
* The fence and its four header lines are this lane's frame; the budget arithmetic and the three
|
|
145
140
|
* degradation tiers belong to {@link renderSkillLinesWithin}, which the drift frame renders through
|
|
@@ -147,42 +142,20 @@ export declare function buildSkillsBlock(skills: ReadonlyArray<SkillListingEntry
|
|
|
147
142
|
* budget cost, for the disclosure lane. */
|
|
148
143
|
export declare function renderSkillsBlock(skills: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): SkillsBlockRender | undefined;
|
|
149
144
|
/**
|
|
150
|
-
* The
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
145
|
+
* The skills VOCABULARY of the one folding engine (`listing-fold.ts` `foldListingWithin`): the full
|
|
146
|
+
* `- name: description` line, the name-only line, the block's cap marker and its overflow count line,
|
|
147
|
+
* measured byte-exact inside a caller-supplied FRAME — the engine both {@link renderSkillsBlock} and
|
|
148
|
+
* the drift frame's ADD arm render through, so the initial `<skills>` block and a later "new skills are
|
|
149
|
+
* now available" announcement degrade by the same rules against the same number — the number the seat
|
|
150
|
+
* reader resolved (`@contract skills.listing_budget_enforced_as_declared`), which is the frame's whole
|
|
151
|
+
* bound: the listing is an intact-class attachment, so no delivery lane measures it a second time.
|
|
157
152
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* [c209-C] R2 C4① — the cap is BYTE-EXACT over the whole rendered frame (see
|
|
162
|
-
* {@link SKILLS_BLOCK_MAX_BYTES}): three honesty tiers, each chosen with the LATER tiers' fixed
|
|
163
|
-
* overhead (cap marker / name-only tail / overflow count line) already reserved so the final
|
|
164
|
-
* render can never blow the budget it was sized against —
|
|
165
|
-
* A. everything fits → every skill gets its full `- name: description` line (byte-identical to
|
|
166
|
-
* the pre-C4 render for every block that was legal under the old cap);
|
|
167
|
-
* B. (CC 220 `sbs` @317547-317586) — reserve the cap marker + EVERY skill's name-only line
|
|
168
|
-
* first, then spend the remaining slack on descriptions in PRIORITY order
|
|
169
|
-
* ({@link SkillListingEntry.declaredRank}), admitting each description whose incremental cost
|
|
170
|
-
* still fits and skipping (not stopping at) the ones that do not. Every skill stays listed;
|
|
171
|
-
* the ones that lose their description are the low-priority ones, not the alphabetically late
|
|
172
|
-
* ones. This replaces the prior "longest full-line PREFIX" cut, which had two defects the
|
|
173
|
-
* anchor does not: the surviving set was decided by name sort order (a skill named `zip-deploy`
|
|
174
|
-
* lost its description to `aardvark-lint` for no reason connected to either skill), and one
|
|
175
|
-
* oversized description truncated the block at itself instead of being skipped over;
|
|
176
|
-
* C. even all-name-only overflows (pathological name census) → list what fits and close with an
|
|
177
|
-
* honest `(… +N more skills …)` count line — the tool still serves all of them.
|
|
178
|
-
*
|
|
179
|
-
* Pure: the same entries, budget and frame always render the same bytes, which is what the
|
|
180
|
-
* prompt-prefix stability contract requires of the listing frames.
|
|
153
|
+
* Tier B follows CC 220 `sbs` @317547-317586 (reserve the marker + every name-only line, then spend
|
|
154
|
+
* the slack on descriptions in {@link SkillListingEntry.declaredRank} order); tier C closes with an
|
|
155
|
+
* honest `(… +N more skills …)` count line — the tool still serves all of them.
|
|
181
156
|
*/
|
|
182
157
|
export declare function renderSkillLinesWithin(skills: ReadonlyArray<SkillListingEntry>, opts: {
|
|
183
|
-
/** The whole frame's byte budget
|
|
184
|
-
* the seat reader: this renderer is reachable from every listing lane, and the ceiling invariant
|
|
185
|
-
* has to hold for a caller that computed its own number. */
|
|
158
|
+
/** The whole frame's byte budget — honoured as given; the seat reader is the one place that decides it. */
|
|
186
159
|
blockMaxBytes: number;
|
|
187
160
|
descMaxChars: number;
|
|
188
161
|
frame: (lines: readonly string[]) => string;
|
|
@@ -203,3 +176,4 @@ export declare function renderSkillLinesWithin(skills: ReadonlyArray<SkillListin
|
|
|
203
176
|
* widening, so it stays safe-by-construction.
|
|
204
177
|
*/
|
|
205
178
|
export declare function createSkillTool(skills: SkillLike[], scope?: ActiveSkillScope): AgentTool;
|
|
179
|
+
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
3
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SKILL_LISTING_DEFAULT_BUDGET, SKILL_LISTING_DESC_MAX_CHARS_DEFAULT } from "../skill-listing-budget.js";
|
|
5
5
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
6
6
|
import { substituteSkillRoot, substituteSkillRootBounded } from "./skill-base-dir.js";
|
|
7
|
+
import { foldListingWithin } from "./listing-fold.js";
|
|
7
8
|
export const OUTPUT_TOOL_NAME = "StructuredOutput";
|
|
8
9
|
export const REPORT_BLOCKED_TOOL_NAME = "ReportBlocked";
|
|
9
10
|
export const REPORT_FINDINGS_TOOL_NAME = "ReportFindings";
|
|
@@ -86,7 +87,6 @@ export function createReportFindingsTool() {
|
|
|
86
87
|
}
|
|
87
88
|
export const SKILL_TOOL_NAME = "Skill";
|
|
88
89
|
export const SKILL_CONTENT_MAX_CHARS = 1024 * 1024;
|
|
89
|
-
export { SKILLS_BLOCK_MAX_BYTES } from "../skill-listing-budget.js";
|
|
90
90
|
export const SKILL_ATTACHMENTS_MAX_CHARS = 50_000;
|
|
91
91
|
export const SKILL_ATTACHMENT_PATH_MAX_CHARS = 512;
|
|
92
92
|
const SKILL_FILES_LIST_MAX_CHARS = 200;
|
|
@@ -130,7 +130,7 @@ const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached — lower-priority skil
|
|
|
130
130
|
export function buildSkillsBlock(skills, budget) {
|
|
131
131
|
return renderSkillsBlock(skills, budget)?.text;
|
|
132
132
|
}
|
|
133
|
-
export function renderSkillsBlock(skills, budget) {
|
|
133
|
+
export function renderSkillsBlock(skills, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
134
134
|
if (skills.length === 0)
|
|
135
135
|
return undefined;
|
|
136
136
|
const headerLines = [
|
|
@@ -141,66 +141,22 @@ export function renderSkillsBlock(skills, budget) {
|
|
|
141
141
|
"",
|
|
142
142
|
];
|
|
143
143
|
return renderSkillLinesWithin(skills, {
|
|
144
|
-
blockMaxBytes: budget
|
|
145
|
-
descMaxChars: budget
|
|
144
|
+
blockMaxBytes: budget.blockMaxBytes,
|
|
145
|
+
descMaxChars: budget.descMaxChars,
|
|
146
146
|
frame: (lines) => [...headerLines, ...lines, "</skills>"].join("\n"),
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
export function renderSkillLinesWithin(skills, opts) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (fullTotal <= bodyBudget)
|
|
161
|
-
return { text: render(fullLines), nameOnly: [], dropped: [] };
|
|
162
|
-
const nameLines = skills.map((s) => `- ${sanitizeUntrustedText(s.name, SHELLED_BODY_ENVELOPE_TAGS)}`);
|
|
163
|
-
const namePrefix = new Array(nameLines.length + 1);
|
|
164
|
-
namePrefix[0] = 0;
|
|
165
|
-
for (let i = 0; i < nameLines.length; i++)
|
|
166
|
-
namePrefix[i + 1] = namePrefix[i] + lineCost(nameLines[i]);
|
|
167
|
-
const markerCost = lineCost(SKILLS_BLOCK_CAP_MARKER);
|
|
168
|
-
const floorCost = markerCost + namePrefix[nameLines.length];
|
|
169
|
-
if (floorCost <= bodyBudget) {
|
|
170
|
-
let slack = bodyBudget - floorCost;
|
|
171
|
-
const byPriority = skills
|
|
172
|
-
.map((s, i) => ({ i, rank: s.declaredRank ?? i }))
|
|
173
|
-
.toSorted((a, b) => a.rank - b.rank || a.i - b.i);
|
|
174
|
-
const withDescription = new Set();
|
|
175
|
-
for (const { i } of byPriority) {
|
|
176
|
-
const delta = lineCost(fullLines[i]) - lineCost(nameLines[i]);
|
|
177
|
-
if (delta <= slack) {
|
|
178
|
-
withDescription.add(i);
|
|
179
|
-
slack -= delta;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, ...skills.map((_s, i) => (withDescription.has(i) ? fullLines[i] : nameLines[i]))]),
|
|
184
|
-
nameOnly: skills.filter((_s, i) => !withDescription.has(i)).map((s) => s.name),
|
|
185
|
-
dropped: [],
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
for (let m = skills.length - 1; m >= 0; m--) {
|
|
189
|
-
const overflowLine = `(… +${skills.length - m} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`;
|
|
190
|
-
const cost = markerCost + namePrefix[m] + lineCost(overflowLine);
|
|
191
|
-
if (cost <= bodyBudget) {
|
|
192
|
-
return {
|
|
193
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, ...nameLines.slice(0, m), overflowLine]),
|
|
194
|
-
nameOnly: skills.slice(0, m).map((s) => s.name),
|
|
195
|
-
dropped: skills.slice(m).map((s) => s.name),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, `(… +${skills.length} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`]),
|
|
201
|
-
nameOnly: [],
|
|
202
|
-
dropped: skills.map((s) => s.name),
|
|
203
|
-
};
|
|
150
|
+
return foldListingWithin(skills, {
|
|
151
|
+
blockMaxBytes: opts.blockMaxBytes,
|
|
152
|
+
full: (s) => skillListingLine(s, opts.descMaxChars),
|
|
153
|
+
nameOnly: (s) => `- ${sanitizeUntrustedText(s.name, SHELLED_BODY_ENVELOPE_TAGS)}`,
|
|
154
|
+
nameOf: (s) => s.name,
|
|
155
|
+
rank: (s, i) => s.declaredRank ?? i,
|
|
156
|
+
capMarker: SKILLS_BLOCK_CAP_MARKER,
|
|
157
|
+
overflowLine: (n) => `(… +${n} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`,
|
|
158
|
+
frame: opts.frame,
|
|
159
|
+
});
|
|
204
160
|
}
|
|
205
161
|
export function createSkillTool(skills, scope) {
|
|
206
162
|
const byName = new Map(skills.map((s) => [s.name, s]));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The task root's CANONICAL spelling — and the one loud outlet for a host that hands in another one.
|
|
3
|
+
*
|
|
4
|
+
* Containment is judged on the canonical form: the roots fence resolves every target through the same
|
|
5
|
+
* `ExecutionEnv` and compares canonical keys, the shell read boundary compares a command's words
|
|
6
|
+
* against the declared roots, and every refusal message quotes them. A deployment that hands the
|
|
7
|
+
* runner a root which is NOT its own realpath (a symlinked temp directory; `/var` → `/private/var` on
|
|
8
|
+
* macOS) therefore reasons in one spelling while the engine judges in another, and a target the
|
|
9
|
+
* deployment considers inside the root can be judged outside it. Before this seat that difference was
|
|
10
|
+
* SILENT — the engine simply canonicalized and carried on.
|
|
11
|
+
*
|
|
12
|
+
* The bad-value discipline's announcement dialect applies (announce, do not refuse): an existing
|
|
13
|
+
* deployment may well be running correctly on the non-canonical spelling, so the judgment is unchanged
|
|
14
|
+
* and only the difference is disclosed. Nothing here repairs the host's spelling either — the engine
|
|
15
|
+
* cannot know which of the two the deployment's own gates key on, and rewriting one into the other
|
|
16
|
+
* would move a fence nobody asked to move.
|
|
17
|
+
*/
|
|
18
|
+
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
19
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
20
|
+
/** The canonical root plus whether the difference was announced (the pin's observable). */
|
|
21
|
+
export interface TaskRootCanonical {
|
|
22
|
+
/** The spelling every fence judges with — the env's canonical form, or the given spelling when the
|
|
23
|
+
* env cannot resolve it (an unresolvable root narrows nothing here; the fence's own skip speaks). */
|
|
24
|
+
canonical: string;
|
|
25
|
+
/** True exactly when the given spelling differed and the notice was delivered. */
|
|
26
|
+
announced: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the run's task root to the spelling its fences judge with, announcing a host spelling that is
|
|
30
|
+
* not that spelling.
|
|
31
|
+
*
|
|
32
|
+
* @contract task_root.canonical_is_the_judge — the engine judges containment on the task root's
|
|
33
|
+
* CANONICAL spelling; when the root the host handed in is not its own canonical form the run announces
|
|
34
|
+
* `config.task_root_not_canonical` exactly once, carrying both spellings, and then judges by the
|
|
35
|
+
* canonical form exactly as before. The notice discloses the difference — it never repairs the host's
|
|
36
|
+
* spelling, never narrows the fence, and never fires for a root that already is its own canonical form.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveTaskRootCanonical(input: {
|
|
39
|
+
env: Pick<ExecutionEnv, "canonicalPath">;
|
|
40
|
+
/** The root as the host spelled it (`deps.rootPath` / the restored workspace root). */
|
|
41
|
+
given: string;
|
|
42
|
+
onNotice: RunnerDeps["onNotice"];
|
|
43
|
+
sessionId: string;
|
|
44
|
+
runId: string;
|
|
45
|
+
}): Promise<TaskRootCanonical>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
+
export async function resolveTaskRootCanonical(input) {
|
|
3
|
+
const c = await input.env.canonicalPath(input.given);
|
|
4
|
+
if (!c.ok)
|
|
5
|
+
return { canonical: input.given, announced: false };
|
|
6
|
+
if (c.value === input.given)
|
|
7
|
+
return { canonical: c.value, announced: false };
|
|
8
|
+
deliverEngineNotice(input.onNotice, {
|
|
9
|
+
code: "config.task_root_not_canonical",
|
|
10
|
+
message: `The task root handed to this run is "${input.given}", which is not its own canonical path ("${c.value}"). ` +
|
|
11
|
+
`Containment is judged on the canonical spelling — the roots fence, the shell read boundary and every ` +
|
|
12
|
+
`refusal message read it — so a target this deployment considers inside the root can be judged outside ` +
|
|
13
|
+
`it whenever the two spellings differ (a symlinked directory; /var → /private/var on macOS). The run ` +
|
|
14
|
+
`proceeds on the canonical spelling exactly as before: hand the canonical path in to make the two agree.`,
|
|
15
|
+
detail: { given: input.given, canonical: c.value, sessionId: input.sessionId, runId: input.runId },
|
|
16
|
+
});
|
|
17
|
+
return { canonical: c.value, announced: true };
|
|
18
|
+
}
|
|
@@ -77,9 +77,90 @@ export declare const CHANGED_FILES_MAX = 20;
|
|
|
77
77
|
* formatter storms remain a documented false-positive source (hash-confirm 抗假阳 = v2).
|
|
78
78
|
*/
|
|
79
79
|
export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
80
|
-
/** Per-boundary
|
|
80
|
+
/** Per-boundary byte cap over the bundle's CLIPPABLE frames (weak-model context economics). Intact-class
|
|
81
|
+
* frames ride outside it — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
81
82
|
export declare const ATTACHMENT_BYTE_CAP: number;
|
|
82
83
|
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "workflow_size_guideline_change" | "budget_usd" | "total_tokens_reminder" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
84
|
+
/** How the bundle cap treats a source — see {@link ATTACHMENT_DELIVERY_CLASS}. */
|
|
85
|
+
export type AttachmentDeliveryClass = {
|
|
86
|
+
readonly class: "clippable";
|
|
87
|
+
} | {
|
|
88
|
+
readonly class: "intact";
|
|
89
|
+
/** The producer's own byte bound for the frame it renders from these inputs — the promise the lane
|
|
90
|
+
* relies on instead of clipping. A closed form of bounded quantities (a resolved budget, a list cap,
|
|
91
|
+
* a per-item cap, a constant), never of unbounded remote text. */
|
|
92
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* @contract attachments.delivery_class — every attachment source is one of two classes, declared HERE
|
|
96
|
+
* and nowhere else. An INTACT source's consumption is "recorded when delivered" (a listing's announced
|
|
97
|
+
* set, a drained pending list, an append-⇔-commit fixed frame), so a clipped frame could never be
|
|
98
|
+
* recorded and would re-render at every boundary; the lane therefore never clips or drops it, and the
|
|
99
|
+
* producer bounds it (`bound`). A CLIPPABLE source is a window-based reminder that re-fires at the next
|
|
100
|
+
* quiet boundary, so the bundle cap ({@link ATTACHMENT_BYTE_CAP}) may clip or drop it. `satisfies
|
|
101
|
+
* Record<AttachmentSource, …>` is the totality guard: a new source that is not classed here — or an
|
|
102
|
+
* intact one without its bound — does not compile.
|
|
103
|
+
*/
|
|
104
|
+
export declare const ATTACHMENT_DELIVERY_CLASS: {
|
|
105
|
+
readonly todo_reminder: {
|
|
106
|
+
readonly class: "clippable";
|
|
107
|
+
};
|
|
108
|
+
readonly task_reminder: {
|
|
109
|
+
readonly class: "clippable";
|
|
110
|
+
};
|
|
111
|
+
readonly tool_search_usage_reminder: {
|
|
112
|
+
readonly class: "clippable";
|
|
113
|
+
};
|
|
114
|
+
readonly changed_files: {
|
|
115
|
+
readonly class: "clippable";
|
|
116
|
+
};
|
|
117
|
+
readonly plan_mode: {
|
|
118
|
+
readonly class: "clippable";
|
|
119
|
+
};
|
|
120
|
+
readonly budget_usd: {
|
|
121
|
+
readonly class: "clippable";
|
|
122
|
+
};
|
|
123
|
+
readonly total_tokens_reminder: {
|
|
124
|
+
readonly class: "clippable";
|
|
125
|
+
};
|
|
126
|
+
readonly background_tasks: {
|
|
127
|
+
readonly class: "clippable";
|
|
128
|
+
};
|
|
129
|
+
readonly tools_delta: {
|
|
130
|
+
readonly class: "intact";
|
|
131
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
132
|
+
};
|
|
133
|
+
readonly agent_listing: {
|
|
134
|
+
readonly class: "intact";
|
|
135
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
136
|
+
};
|
|
137
|
+
readonly skills_listing: {
|
|
138
|
+
readonly class: "intact";
|
|
139
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
140
|
+
};
|
|
141
|
+
readonly mcp_instructions: {
|
|
142
|
+
readonly class: "intact";
|
|
143
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
144
|
+
};
|
|
145
|
+
readonly mcp_dropped_tools: {
|
|
146
|
+
readonly class: "intact";
|
|
147
|
+
readonly bound: (inp: AttachmentInputs) => number;
|
|
148
|
+
};
|
|
149
|
+
readonly date_change: {
|
|
150
|
+
readonly class: "intact";
|
|
151
|
+
readonly bound: () => number;
|
|
152
|
+
};
|
|
153
|
+
readonly instructions_change: {
|
|
154
|
+
readonly class: "intact";
|
|
155
|
+
readonly bound: () => number;
|
|
156
|
+
};
|
|
157
|
+
readonly workflow_size_guideline_change: {
|
|
158
|
+
readonly class: "intact";
|
|
159
|
+
readonly bound: () => number;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
/** Whether the bundle cap must leave this source whole ({@link ATTACHMENT_DELIVERY_CLASS}). */
|
|
163
|
+
export declare function isIntactSource(source: AttachmentSource): boolean;
|
|
83
164
|
/** G1 续批 — one agent type on the delegation tool's roster (ToolSpec.agentListing, filled by
|
|
84
165
|
* createSubagentTool: defined agents + the built-in fork when offered). */
|
|
85
166
|
export interface AgentListingEntry {
|
|
@@ -196,14 +277,6 @@ export interface AttachmentState {
|
|
|
196
277
|
* `announcedListings.models` so a catalog change across a suspend IS re-announced (the drift
|
|
197
278
|
* line rides the first boundary's agent_listing frame). */
|
|
198
279
|
announcedModels?: readonly string[];
|
|
199
|
-
/** [c209-C] R2 C4② — same-boundary anti-starvation latch: set by {@link collectDueAttachments}
|
|
200
|
-
* when a rendered skills_listing block did NOT survive its own bundle intact (clipped/starved —
|
|
201
|
-
* typically because the higher-priority agent_listing spent the byte budget first), cleared when
|
|
202
|
-
* one survives. While set, the NEXT boundary orders skills_listing BEFORE agent_listing, so two
|
|
203
|
-
* listing frames that cannot share one 8KB bundle alternate to completion instead of the
|
|
204
|
-
* agent frame permanently starving the skills frame (each frame individually fits the cap by
|
|
205
|
-
* construction — agent roster ≤ cap by deployment size, skills block ≤ SKILLS_BLOCK_MAX_BYTES). */
|
|
206
|
-
skillsListingStarved?: boolean;
|
|
207
280
|
/**
|
|
208
281
|
* The `padded-countdown` arm's SMOOTHING FLOOR: the highest usage figure this run has
|
|
209
282
|
* already published against. CC's `cumulativeUsed` (bundle: `Math.max(storedFloor, rolledOver +
|
|
@@ -546,9 +619,10 @@ export interface AttachmentInputs {
|
|
|
546
619
|
* the delta path serves dynamic-installation deployments and tests, same posture as
|
|
547
620
|
* `agentListing`). Only passed when `config.skillsListing` is on. */
|
|
548
621
|
skillsListing?: ReadonlyArray<SkillListingEntry>;
|
|
549
|
-
/** The
|
|
550
|
-
*
|
|
551
|
-
|
|
622
|
+
/** The listing family's budget, resolved once at the config door: `agent_listing` and `skills_listing`
|
|
623
|
+
* are each folded into it (each frame measured against the whole number, not a share). Absent ⇒ the
|
|
624
|
+
* seat reader's no-knob answer. */
|
|
625
|
+
listingBudget?: ResolvedSkillListingBudget;
|
|
552
626
|
/** G1 续批 mcp_instructions: pending deltas snapshotted from `MaterializedMcp.instructionsDelta`.
|
|
553
627
|
* CALLER-owned consumption on intact cap survival (same `newTools` discipline). */
|
|
554
628
|
mcpInstructions?: {
|
|
@@ -823,11 +897,24 @@ export declare function agentListingDeltaHeader(toolName: string): string;
|
|
|
823
897
|
* no re-render byte-identity exposure of its own.
|
|
824
898
|
*
|
|
825
899
|
* PURE — never mutates `state`. The announced-set advance is CALLER-owned via
|
|
826
|
-
* {@link commitAgentListing}, committed
|
|
827
|
-
*
|
|
828
|
-
*
|
|
900
|
+
* {@link commitAgentListing}, committed on the carrying message's receipt (`FrameReceipt`).
|
|
901
|
+
*
|
|
902
|
+
* The roster is folded into the listing budget by the one folding engine ({@link foldListingWithin}),
|
|
903
|
+
* the skills block's sibling: every type stays listed, the last-declared ones lose their detail first,
|
|
904
|
+
* and the frame's size is this producer's own promise ({@link ATTACHMENT_DELIVERY_CLASS}). The report
|
|
905
|
+
* (`nameOnly` / `dropped`) covers the roster on an initial frame and the added set on a drift frame.
|
|
829
906
|
*/
|
|
830
|
-
export declare function
|
|
907
|
+
export declare function renderAgentListingFrame(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, toolName: string, models?: readonly string[], budget?: ResolvedSkillListingBudget): AgentListingFrameRender | undefined;
|
|
908
|
+
/** The text-only face of {@link renderAgentListingFrame} — what the delivery lanes render. */
|
|
909
|
+
export declare function renderAgentListingDelta(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, toolName: string, models?: readonly string[], budget?: ResolvedSkillListingBudget): string | undefined;
|
|
910
|
+
/** What one {@link renderAgentListingFrame} call produced: the frame, the report, and how many types it set out to list. */
|
|
911
|
+
export interface AgentListingFrameRender {
|
|
912
|
+
text: string;
|
|
913
|
+
nameOnly: readonly string[];
|
|
914
|
+
dropped: readonly string[];
|
|
915
|
+
/** The denominator for the disclosure: the whole roster on an initial frame, the added set on a drift frame. */
|
|
916
|
+
listed: number;
|
|
917
|
+
}
|
|
831
918
|
/** [c209-C] R2 C7 — the Q4 catalog line, single-sourced for the initial frame's tail AND the
|
|
832
919
|
* drift re-announce arm so the two can never diverge byte-wise. [c209-C] 收口 C7 补口: an
|
|
833
920
|
* EMPTY catalog renders an honest "(none)" — reachable only from the DRIFT arm (a known announced
|
|
@@ -841,9 +928,8 @@ export declare const MODELS_AVAILABLE_PREFIX = "Models available for the 'model'
|
|
|
841
928
|
export declare function replayAnnouncedModels(texts: readonly string[]): readonly string[] | undefined;
|
|
842
929
|
/**
|
|
843
930
|
* MED-3① commit half of {@link renderAgentListingDelta}: mark the CURRENT roster as announced. The run
|
|
844
|
-
* loop calls this
|
|
845
|
-
*
|
|
846
|
-
* announced map untouched so the next quiet boundary re-announces. Snapshotting `entries` wholesale is
|
|
931
|
+
* loop calls this on the receipt of the message that carried the frame; an undelivered frame leaves the
|
|
932
|
+
* announced map untouched so the next boundary re-announces. Snapshotting `entries` wholesale is
|
|
847
933
|
* equivalent to add/remove bookkeeping (the diff is name-keyed; descriptions are never re-diffed).
|
|
848
934
|
*/
|
|
849
935
|
export declare function commitAgentListing(state: AttachmentState, entries: ReadonlyArray<AgentListingEntry>, models?: readonly string[]): void;
|
|
@@ -874,10 +960,8 @@ export interface SkillsListingFrameRender extends SkillsBlockRender {
|
|
|
874
960
|
*
|
|
875
961
|
* ONE RULER FOR EVERY FRAME: the drift frame is measured against the SAME resolved budget as the
|
|
876
962
|
* initial block, arms and joiners included, and its ADD arm degrades through the same three tiers
|
|
877
|
-
* ({@link renderSkillLinesWithin}).
|
|
878
|
-
*
|
|
879
|
-
* body, a clipped body never equals the intact-survival re-render, and the frame is re-rendered and
|
|
880
|
-
* re-clipped at every boundary for the life of the task. The split: the ambient tail and the removal
|
|
963
|
+
* ({@link renderSkillLinesWithin}). The frame is an intact-class attachment — the lane never clips
|
|
964
|
+
* it — so this measurement is its whole bound. The split: the ambient tail and the removal
|
|
881
965
|
* arm's one-line floor are reserved first (both constants), the ADD arm degrades into what is left,
|
|
882
966
|
* and the removal arm spends whatever the add arm did not — so the frame's size is the resolved budget
|
|
883
967
|
* or, when even the two floors do not fit it, a small constant, never a function of the roster.
|
|
@@ -886,7 +970,7 @@ export interface SkillsListingFrameRender extends SkillsBlockRender {
|
|
|
886
970
|
* description or could not list at all. A departed name that did not fit is not a listed skill.
|
|
887
971
|
*
|
|
888
972
|
* MED-3① discipline: PURE — the announced-set advance is CALLER-owned via {@link commitSkillsListing},
|
|
889
|
-
* committed
|
|
973
|
+
* committed on the carrying message's receipt. The commit records
|
|
890
974
|
* EVERY entry, including one this frame dropped: the alternative is a frame re-rendering the same
|
|
891
975
|
* un-listable tail at every boundary, which is the replay this budget exists to end. The `Skill` tool
|
|
892
976
|
* serves a dropped skill, and `skills.listing_truncated` tells the deployment it happened.
|
|
@@ -896,19 +980,16 @@ export declare function renderSkillsListingFrame(state: AttachmentState, entries
|
|
|
896
980
|
* the intact-survival predicate re-renders to compare. */
|
|
897
981
|
export declare function renderSkillsListingDelta(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): string | undefined;
|
|
898
982
|
/** [c209-C] commit half of {@link renderSkillsListingDelta} — same MED-3① contract as
|
|
899
|
-
* {@link commitAgentListing}: called by the run loop
|
|
900
|
-
*
|
|
901
|
-
* announced map untouched so the next quiet boundary re-announces. */
|
|
983
|
+
* {@link commitAgentListing}: called by the run loop on the receipt of the message that carried the
|
|
984
|
+
* frame; an undelivered frame leaves the announced map untouched so the next boundary re-announces. */
|
|
902
985
|
export declare function commitSkillsListing(state: AttachmentState, entries: ReadonlyArray<SkillListingEntry>): void;
|
|
903
986
|
/**
|
|
904
987
|
* Per-server bound (in CODE POINTS, matching every other clipper in `untrusted-text.ts`) on
|
|
905
|
-
* ONE MCP server's `instructions` text. The text is SERVER-controlled and UNBOUNDED on the wire
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
910
|
-
* Sized at the delta lane's own order of magnitude (the 8KB frame budget), leaving fence + header
|
|
911
|
-
* room under the bundle cap once the "…" truncation marker lands.
|
|
988
|
+
* ONE MCP server's `instructions` text. The text is SERVER-controlled and UNBOUNDED on the wire, and
|
|
989
|
+
* neither lane it rides has a cap above this one: the stable system-prompt lane has none, and the
|
|
990
|
+
* `mcp_instructions` attachment is intact-class (the bundle cap never clips it), so this bound IS the
|
|
991
|
+
* producer's promise — without it a single server could pin an arbitrarily large blob into the cached
|
|
992
|
+
* prompt prefix or into one boundary frame.
|
|
912
993
|
*/
|
|
913
994
|
export declare const MCP_INSTRUCTIONS_MAX_CHARS: number;
|
|
914
995
|
/**
|
|
@@ -942,8 +1023,8 @@ export declare function fenceMcpServerInstructions(server: string, text: string)
|
|
|
942
1023
|
* the text is additionally already `sanitizeUntrustedText`-neutralized at the mcp.ts producer).
|
|
943
1024
|
* The fence is now applied through {@link fenceMcpServerInstructions}, shared with the
|
|
944
1025
|
* STABLE system-prompt lane, so the two lanes can never again carry the same text with different
|
|
945
|
-
* trust postures — and it carries a per-server {@link MCP_INSTRUCTIONS_MAX_CHARS} bound (
|
|
946
|
-
*
|
|
1026
|
+
* trust postures — and it carries a per-server {@link MCP_INSTRUCTIONS_MAX_CHARS} bound (the
|
|
1027
|
+
* frame is intact-class: its size is this producer's promise, servers × that cap).
|
|
947
1028
|
* The disconnect list is followed by the CC-verbatim {@link AMBIENT_CONTEXT_NOTE} (CC `Emr`,
|
|
948
1029
|
* pretty.js:599240/599992 — resolved by the 2026-07-10 parity spot audit).
|
|
949
1030
|
* Removal names are `\n`-joined WITHOUT a `- ` prefix (CC-exact; the agent-listing removal list DOES
|
|
@@ -974,21 +1055,16 @@ export declare function renderMcpInstructionsDelta(added: ReadonlyArray<{
|
|
|
974
1055
|
* loop's intact-survival consumption check (tools_delta discipline).
|
|
975
1056
|
*/
|
|
976
1057
|
export declare function renderMcpDroppedTools(entries: ReadonlyArray<McpDroppedTool>): string;
|
|
977
|
-
/**
|
|
978
|
-
*
|
|
1058
|
+
/** Per-boundary byte bound for one mcp_dropped_tools frame — the producer's own promise for an
|
|
1059
|
+
* intact-class frame ({@link ATTACHMENT_DELIVERY_CLASS}), so one batch reads as one bounded frame. */
|
|
979
1060
|
export declare const MCP_DROPPED_BATCH_MAX_BYTES = 4096;
|
|
980
1061
|
/**
|
|
981
|
-
*
|
|
982
|
-
* render
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
* tail rides later boundaries. Pure/deterministic so the drain predicate can re-derive the exact
|
|
988
|
-
* snapshot. the ≥1 floor is SAFE only because entries are bounded at the mcp.ts
|
|
989
|
-
* producer (tool ≤160 cps, reason ≤240 cps, both single-lined) — one rendered entry is always a
|
|
990
|
-
* few hundred bytes, far under both this bound and the 8KB bundle cap, so the single-entry frame
|
|
991
|
-
* can never be cap-clipped into a permanent re-render wedge.
|
|
1062
|
+
* Bounded-batch selection for the dropped-tools announce: the longest prefix of COMPLETE entries whose
|
|
1063
|
+
* render fits {@link MCP_DROPPED_BATCH_MAX_BYTES} (always ≥1), so an oversized list drains
|
|
1064
|
+
* monotonically across boundaries — the run loop splices exactly the delivered batch, the tail rides
|
|
1065
|
+
* later boundaries. Pure/deterministic so the drain predicate can re-derive the exact snapshot. The ≥1
|
|
1066
|
+
* floor is SAFE because entries are bounded at the mcp.ts producer (tool ≤160 cps, reason ≤240 cps,
|
|
1067
|
+
* both single-lined) — one rendered entry is always a few hundred bytes.
|
|
992
1068
|
*/
|
|
993
1069
|
export declare function selectMcpDroppedBatch<T extends McpDroppedTool>(entries: ReadonlyArray<T>): T[];
|
|
994
1070
|
/**
|