@sema-agent/core 7.21.1 → 7.23.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 +290 -1
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +2 -1
- package/dist/core/ask-origin.d.ts +1 -0
- package/dist/core/ask-origin.js +3 -2
- package/dist/core/auto-compaction.d.ts +2 -2
- package/dist/core/auto-mode-rebuild.js +6 -2
- package/dist/core/auto-mode.d.ts +40 -18
- package/dist/core/auto-mode.js +63 -45
- package/dist/core/checkpoint-store.d.ts +9 -13
- package/dist/core/context-edit.js +1 -1
- package/dist/core/engine-notice.d.ts +62 -1
- package/dist/core/engine-notice.js +14 -1
- package/dist/core/gate-bounded-await.d.ts +13 -3
- package/dist/core/gate-fold.js +3 -3
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +6 -0
- package/dist/core/mcp.d.ts +65 -0
- package/dist/core/mcp.js +190 -41
- package/dist/core/memory-engine/consolidation.d.ts +5 -0
- package/dist/core/memory-engine/consolidation.js +7 -3
- package/dist/core/memory-engine/engine.d.ts +8 -0
- package/dist/core/memory-engine/engine.js +25 -19
- package/dist/core/memory-engine/layout.d.ts +47 -9
- package/dist/core/memory-engine/layout.js +157 -57
- package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
- package/dist/core/memory-engine/session-incarnation.js +63 -0
- package/dist/core/parent-spec-seat.d.ts +15 -3
- package/dist/core/parent-spec-seat.js +5 -0
- package/dist/core/park-selfcheck.js +1 -1
- package/dist/core/path-identity.d.ts +11 -4
- package/dist/core/path-identity.js +5 -2
- package/dist/core/permission-rule-model.d.ts +1 -2
- package/dist/core/read-root-candidate.js +11 -1
- package/dist/core/retention.d.ts +8 -1
- package/dist/core/retention.js +2 -0
- package/dist/core/runner/assemble-result.d.ts +2 -7
- package/dist/core/runner/assemble-result.js +25 -8
- package/dist/core/runner/classifier-ladder.js +1 -0
- package/dist/core/runner/contracts.d.ts +26 -15
- package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
- package/dist/core/runner/inherited-ask-grants.js +1 -0
- package/dist/core/runner/mcp-redial.d.ts +109 -0
- package/dist/core/runner/mcp-redial.js +60 -0
- package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
- package/dist/core/runner/prepare-context-lane.d.ts +43 -1
- package/dist/core/runner/prepare-context-lane.js +25 -4
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
- package/dist/core/runner/prepare-hands-readface.js +13 -3
- package/dist/core/runner/prepare-memory.js +3 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
- package/dist/core/runner/prepare-protocol-tools.js +20 -38
- package/dist/core/runner/prepare-task.js +4 -4
- package/dist/core/runner/prepare-turn-wiring.js +2 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
- package/dist/core/runner/prepare-wiring-manifest.js +2 -1
- package/dist/core/runner/run-harness-handlers.js +4 -2
- package/dist/core/runner/run-identity-wiring.d.ts +3 -1
- package/dist/core/runner/run-identity-wiring.js +4 -1
- package/dist/core/runner/run-recovery-lanes.js +6 -3
- package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
- package/dist/core/runner/run-settle-and-teardown.js +6 -2
- package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
- package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
- package/dist/core/runner/run-terminal-adoption.js +2 -6
- package/dist/core/runner/runtask.js +8 -5
- package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
- package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
- package/dist/core/runner/write-protection-station.d.ts +7 -25
- package/dist/core/runner/write-protection-station.js +18 -0
- package/dist/core/runner-deps.d.ts +16 -7
- package/dist/core/skill-listing-budget.d.ts +5 -4
- package/dist/core/task-registry.d.ts +0 -6
- package/dist/core/task-result.d.ts +31 -12
- package/dist/core/task-spec.d.ts +2 -1
- package/dist/core/task-stream.d.ts +26 -0
- package/dist/core/tool-spec.d.ts +5 -1
- package/dist/core/trace.d.ts +5 -0
- package/dist/core/types.d.ts +3 -3
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +23 -0
- package/dist/core/wiring-manifest.js +3 -0
- package/dist/core/write-protect.d.ts +64 -12
- package/dist/core/write-protect.js +88 -34
- package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
- package/dist/engine/execution-env/node-execution-env.js +5 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/orchestration/workflow-governance.js +1 -1
- package/dist/orchestration/workflow-primitives.d.ts +2 -2
- package/dist/tools/fs/bash-program-position.d.ts +26 -5
- package/dist/tools/fs/bash-program-position.js +63 -16
- package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
- package/dist/tools/fs/bash-readonly-classifier.js +134 -55
- package/dist/tools/fs/fs-bash.js +43 -18
- package/dist/tools/fs/fs-read.js +2 -1
- package/dist/tools/fs/fs-shared.d.ts +33 -7
- package/dist/tools/fs/fs-shared.js +12 -1
- package/dist/tools/fs/index.d.ts +4 -2
- package/dist/tools/fs/read-deny.d.ts +25 -11
- package/dist/tools/fs/read-deny.js +5 -1
- package/dist/tools/fs/safety.d.ts +38 -4
- package/dist/tools/fs/safety.js +31 -3
- package/package.json +2 -2
- package/test/export-surface.snapshot.json +11 -1
- package/wire-changes/core@7.22.0.json +637 -0
- package/wire-changes/core@7.23.0.json +316 -0
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -26,11 +26,7 @@ function operandFamily(paths) {
|
|
|
26
26
|
}
|
|
27
27
|
export function bashReversibilityProbe(allow, boundary) {
|
|
28
28
|
const allowSet = new Set(allow ?? bashClassifyDefaultAllow());
|
|
29
|
-
|
|
30
|
-
const command = shellCommandOf(args);
|
|
31
|
-
if (command === undefined)
|
|
32
|
-
return { reversible: false };
|
|
33
|
-
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
29
|
+
const judge = (args, command, resolved, offers) => {
|
|
34
30
|
const boundaryGate = (verdict) => readBoundaryMandate(command, allowSet, resolved, verdict);
|
|
35
31
|
const outOfRootGate = () => readBoundaryMandate(command, allowSet, resolved, {});
|
|
36
32
|
const backgrounded = isBackgroundShellCall(args);
|
|
@@ -49,9 +45,20 @@ export function bashReversibilityProbe(allow, boundary) {
|
|
|
49
45
|
const loop = classifyBoundedReadonlyPollLoopDetailed(command, allowSet, resolved);
|
|
50
46
|
if (loop.reason === undefined)
|
|
51
47
|
return { reversible: true };
|
|
48
|
+
const seatOffer = (verdict) => (offers ? readRootCandidateOf(verdict) : {});
|
|
52
49
|
if (boundaryDeclared(loop) || unjudgedRead(command, allowSet, resolved, loop))
|
|
53
|
-
return { reversible: false, mandated: true, ...
|
|
54
|
-
|
|
50
|
+
return { reversible: false, mandated: true, ...seatOffer(loop) };
|
|
51
|
+
const refusedCause = recursiveReadCause(detailed);
|
|
52
|
+
return { reversible: false, ...boundaryGate(detailed), ...seatOffer(detailed), ...(refusedCause !== undefined ? { cause: refusedCause } : {}) };
|
|
53
|
+
};
|
|
54
|
+
return (args) => {
|
|
55
|
+
const command = shellCommandOf(args);
|
|
56
|
+
if (command === undefined)
|
|
57
|
+
return { reversible: false };
|
|
58
|
+
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
59
|
+
if (resolved === undefined)
|
|
60
|
+
return judge(args, command, undefined, true);
|
|
61
|
+
return grantClearsSeat((c, b, o) => judge(args, c, b, o), command, resolved);
|
|
55
62
|
};
|
|
56
63
|
}
|
|
57
64
|
function boundaryUnread(verdict) {
|
|
@@ -59,7 +66,9 @@ function boundaryUnread(verdict) {
|
|
|
59
66
|
}
|
|
60
67
|
function readRootCandidateOf(verdict) {
|
|
61
68
|
const dir = verdict.outOfRootGrantCandidate;
|
|
62
|
-
|
|
69
|
+
if (dir === undefined)
|
|
70
|
+
return {};
|
|
71
|
+
return { readRootCandidate: { dir, clearsThisAsk: true, ...(verdict.outOfRootGrantCoversExactPath === true ? { covers: "exact" } : {}) } };
|
|
63
72
|
}
|
|
64
73
|
function boundaryUndecided(verdict) {
|
|
65
74
|
return verdict.undecidedPaths !== undefined && verdict.undecidedPaths.length > 0;
|
|
@@ -110,20 +119,16 @@ function recursiveReadCause(detailed) {
|
|
|
110
119
|
}
|
|
111
120
|
export function bashReadBoundaryProbe(boundary) {
|
|
112
121
|
const allowSet = new Set(bashClassifyDefaultAllow());
|
|
113
|
-
|
|
114
|
-
const command = shellCommandOf(args);
|
|
115
|
-
if (command === undefined)
|
|
116
|
-
return { reversible: false };
|
|
117
|
-
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
118
|
-
if (resolved === undefined)
|
|
119
|
-
return { reversible: true };
|
|
122
|
+
const judge = (command, resolved, offers) => {
|
|
120
123
|
const seatVerdict = (verdict) => {
|
|
121
124
|
if (boundaryDeclared(verdict) || unjudgedRead(command, allowSet, resolved, verdict)) {
|
|
122
125
|
const cause = recursiveReadCause(verdict);
|
|
123
126
|
return { reversible: false, mandated: true, ...(cause !== undefined ? { cause } : {}) };
|
|
124
127
|
}
|
|
125
|
-
if (boundaryUndecided(verdict))
|
|
126
|
-
|
|
128
|
+
if (boundaryUndecided(verdict)) {
|
|
129
|
+
const cause = recursiveReadCause(verdict);
|
|
130
|
+
return { reversible: false, ...(cause !== undefined ? { cause } : {}) };
|
|
131
|
+
}
|
|
127
132
|
if (verdict.reason === undefined)
|
|
128
133
|
return { reversible: true };
|
|
129
134
|
return undefined;
|
|
@@ -137,12 +142,32 @@ export function bashReadBoundaryProbe(boundary) {
|
|
|
137
142
|
const compoundVerdict = seatVerdict(detailed);
|
|
138
143
|
const loopVerdict = seatVerdict(loop);
|
|
139
144
|
const mandated = [compoundVerdict, loopVerdict].find((verdict) => verdict?.mandated === true);
|
|
145
|
+
const offerFrom = (verdict) => (offers ? readRootCandidateOf(verdict) : {});
|
|
140
146
|
if (mandated !== undefined)
|
|
141
|
-
return { ...mandated, ...
|
|
147
|
+
return { ...mandated, ...offerFrom(detailed), ...offerFrom(loop) };
|
|
142
148
|
if (readBoundaryMandate(command, allowSet, resolved, detailed).mandated === true)
|
|
143
149
|
return { reversible: false, mandated: true };
|
|
144
150
|
return compoundVerdict ?? loopVerdict ?? { reversible: true };
|
|
145
151
|
};
|
|
152
|
+
return (args) => {
|
|
153
|
+
const command = shellCommandOf(args);
|
|
154
|
+
if (command === undefined)
|
|
155
|
+
return { reversible: false };
|
|
156
|
+
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
157
|
+
if (resolved === undefined)
|
|
158
|
+
return { reversible: true };
|
|
159
|
+
return grantClearsSeat(judge, command, resolved);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function grantClearsSeat(judge, command, resolved) {
|
|
163
|
+
const verdict = judge(command, resolved, true);
|
|
164
|
+
const dir = verdict.readRootCandidate?.dir;
|
|
165
|
+
if (dir === undefined)
|
|
166
|
+
return verdict;
|
|
167
|
+
if (judge(command, { ...resolved, roots: [...resolved.roots, dir] }, false).reversible)
|
|
168
|
+
return verdict;
|
|
169
|
+
const { readRootCandidate: _withdrawn, ...withoutOffer } = verdict;
|
|
170
|
+
return withoutOffer;
|
|
146
171
|
}
|
|
147
172
|
export { FULL_SHELL_CONTRACT_ID } from "../../core/tool-catalog-entries.js";
|
|
148
173
|
const EXIT1_INTERPRETATION = {
|
package/dist/tools/fs/fs-read.js
CHANGED
|
@@ -231,7 +231,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
231
231
|
if (prevNb && !prevNb.isPartialView && prevNb.hash === hash && prevNb.view && prevNb.view.start === 1 && prevNb.view.end === total) {
|
|
232
232
|
return fileUnchangedResult(`[${path}: unchanged since you last read it (lines 1-${total} of ${total}); content omitted to save context]`, "unchanged-since-last-read", 1, total, total);
|
|
233
233
|
}
|
|
234
|
-
state.set(r.key, { hash, totalLines: countLines(content), truncated: false, view: { start: 1, end: total }, lastReadAt: Date.now() });
|
|
234
|
+
state.set(r.key, { hash, totalLines: countLines(content), truncated: false, view: { start: 1, end: total }, lastReadAt: Date.now(), transmittedByToolCallId: ctx.toolCallId });
|
|
235
235
|
const bodyBlocks = rendered.blocks.length > 0 ? rendered.blocks : [{ type: "text", text: "[notebook has 0 cells]" }];
|
|
236
236
|
const nbDisclosure = discloseReminderShaped({
|
|
237
237
|
segments: rendered.blocks.filter((b) => b.type === "text").map((b) => b.text),
|
|
@@ -307,6 +307,7 @@ export function createReadFileTool(env, state, rootCanonical, cwdRef, additional
|
|
|
307
307
|
...(pageMarker !== undefined ? { isPartialView: true } : {}),
|
|
308
308
|
view: { start, end },
|
|
309
309
|
lastReadAt: Date.now(),
|
|
310
|
+
transmittedByToolCallId: ctx.toolCallId,
|
|
310
311
|
});
|
|
311
312
|
if (total === 0)
|
|
312
313
|
return `${openSystemReminder(reminderMark)}Warning: the file exists but the contents are empty.</system-reminder>`;
|
|
@@ -375,9 +375,7 @@ export declare const CWD_SENTINEL = "__cc_cwd_9f2c1b__";
|
|
|
375
375
|
* TWO CALLERS, ONE RULE, which is why the marker is optional. The foreground result path KNOWS the
|
|
376
376
|
* marker it minted and passes it (exact, unambiguous). A backgrounded command's spool is assembled by a
|
|
377
377
|
* different lane entirely — the shell row outlives the run that launched it and never had the marker —
|
|
378
|
-
* so it passes none and the marker is recovered per span from the sentinel plus its hex suffix.
|
|
379
|
-
* this was shared, only the foreground path stripped, and the completion notification handed a person
|
|
380
|
-
* the raw internal marker with the directory inside it.
|
|
378
|
+
* so it passes none and the marker is recovered per span from the sentinel plus its hex suffix.
|
|
381
379
|
*/
|
|
382
380
|
export declare function stripCwdSentinel(stderr: string, marker?: string): {
|
|
383
381
|
stderr: string;
|
|
@@ -504,10 +502,38 @@ export declare function inheritSessionReadFileState(target: ReadFileState, inher
|
|
|
504
502
|
* coordinate (canonicalized the same way the Read/Edit/Write tools populate it), so a plain lookup
|
|
505
503
|
* suffices; no re-hash/re-register needed since the entry's content genuinely has not changed.
|
|
506
504
|
*/
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
505
|
+
/**
|
|
506
|
+
* The SECOND way a transmission leaves the model's view, and why this function is named for context
|
|
507
|
+
* SHRINK rather than compaction: the request-layer stale-result pass replaces an old tool result's
|
|
508
|
+
* content with a cleared marker — no summary, no re-attachment, nothing changed in the session — and
|
|
509
|
+
* for a Read result the effect on the entry is identical: the body its dedup stub points at is no
|
|
510
|
+
* longer anywhere the model can see, so the entry must retire with it.
|
|
511
|
+
*
|
|
512
|
+
* Selection is by the TRANSMISSION, not by path — an entry a LATER Read re-transmitted carries that
|
|
513
|
+
* later call's id and so survives a clear of the earlier one, which a path-keyed retirement could not
|
|
514
|
+
* tell apart. KNOWN IMPRECISION: a call id is not unique over a run (the clear ledger keys occurrences
|
|
515
|
+
* by id + message timestamp + ordinal for that reason) and the finer key is not reachable at the Read
|
|
516
|
+
* mint, which does not know the timestamp of the message its result will land in. A reused id can
|
|
517
|
+
* therefore retire one entry too many, costing a re-transmission and a tightened read-before-edit gate
|
|
518
|
+
* — the same tightening the summarized arm accepts wholesale. The converse is not reachable: an entry
|
|
519
|
+
* carries the id of the last Read that transmitted it, so if that result was cleared its id is in the list.
|
|
520
|
+
*/
|
|
521
|
+
export type ReadFileStateShrink =
|
|
522
|
+
/** A landed compaction: `attachedComplete` re-entered the context whole; `preserveKeys` stayed visible in the kept tail. */
|
|
523
|
+
{
|
|
524
|
+
kind: "summarized";
|
|
525
|
+
attachedComplete: ReadonlyArray<{
|
|
526
|
+
path: string;
|
|
527
|
+
content: string;
|
|
528
|
+
}>;
|
|
529
|
+
preserveKeys?: ReadonlyArray<string>;
|
|
530
|
+
}
|
|
531
|
+
/** A request-layer stale-result clear: these tool calls' results are markers now. Nothing re-entered. */
|
|
532
|
+
| {
|
|
533
|
+
kind: "tool-results-cleared";
|
|
534
|
+
clearedToolCallIds: ReadonlyArray<string>;
|
|
535
|
+
};
|
|
536
|
+
export declare function applyContextShrinkToReadFileState(state: ReadFileState, shrink: ReadFileStateShrink): void;
|
|
511
537
|
/** CC 2.1.198 verbatim skeleton: the ENOENT error body for a
|
|
512
538
|
* missing Read/Edit target — `File does not exist. Note: your current working directory is <cwd>.`
|
|
513
539
|
* plus a `Did you mean <sibling>?` correction when the parent directory holds a near-name (case
|
|
@@ -318,7 +318,18 @@ export async function inheritSessionReadFileState(target, inherited, judgeKey) {
|
|
|
318
318
|
target.set(judged, entry);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
-
export function
|
|
321
|
+
export function applyContextShrinkToReadFileState(state, shrink) {
|
|
322
|
+
if (shrink.kind === "tool-results-cleared") {
|
|
323
|
+
if (shrink.clearedToolCallIds.length === 0)
|
|
324
|
+
return;
|
|
325
|
+
const cleared = new Set(shrink.clearedToolCallIds);
|
|
326
|
+
for (const [k, v] of [...state]) {
|
|
327
|
+
if (v.transmittedByToolCallId !== undefined && cleared.has(v.transmittedByToolCallId))
|
|
328
|
+
state.delete(k);
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const { attachedComplete, preserveKeys = [] } = shrink;
|
|
322
333
|
const preserve = new Set(preserveKeys);
|
|
323
334
|
for (const [k, v] of [...state]) {
|
|
324
335
|
if (v.seededFromContext !== true && !preserve.has(k))
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -160,9 +160,11 @@ export interface HandsToolkitOptions {
|
|
|
160
160
|
* time. The write faces are untouched (their guard is createSensitivePathPolicy). */
|
|
161
161
|
readDenyPatterns?: readonly ReadDenyEntry[];
|
|
162
162
|
/** The deployment's built-in-table TIER selection ({@link import("./read-deny.js").ReadDenyBuiltinConfig}
|
|
163
|
-
* `tiers` key)
|
|
163
|
+
* `tiers` key) — the ON/OFF switch for the built-in table, OFF unless this seat says otherwise:
|
|
164
|
+
* EXACTLY the listed tiers of {@link import("./read-deny.js").READ_FACE_BUILTIN_DENY_TABLE}
|
|
164
165
|
* are active (`[]` = none — explicit and legal); absent = the default selection
|
|
165
|
-
* ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}
|
|
166
|
+
* ({@link import("./read-deny.js").READ_DENY_DEFAULT_TIERS}), which is EMPTY — an unconfigured
|
|
167
|
+
* mount activates no built-in row.
|
|
166
168
|
* Composes with {@link readDenyBuiltinExclude} (tier selection first, then row removal) and never
|
|
167
169
|
* touches the additions seats. Unknown tier names throw at wiring time. Deployment seat ONLY —
|
|
168
170
|
* deliberately not a TaskSpec key, so no task can widen below its deployment. */
|
|
@@ -82,8 +82,9 @@ export interface ReadDenyBuiltinRow {
|
|
|
82
82
|
/**
|
|
83
83
|
* READ_FACE_BUILTIN_DENY_TABLE — the FULL built-in table with tier metadata: the material basis of
|
|
84
84
|
* the deployment admin face, what an org administrator "sees" before selecting tiers / excluding
|
|
85
|
-
* rows. Activation is a
|
|
86
|
-
* {@link READ_DENY_DEFAULT_TIERS}
|
|
85
|
+
* rows. Activation is a SEPARATE fact and is OFF by default: every row here is inert until a
|
|
86
|
+
* deployment names its tier ({@link READ_DENY_DEFAULT_TIERS} is empty), so this is the MENU, never
|
|
87
|
+
* the active set. Rows stay curated regardless — the menu has to be worth switching on.
|
|
87
88
|
* The dotenv family is deliberately NOT here — workspace `.env` files are working material for the
|
|
88
89
|
* tasks this engine runs; an `outside_workspace` tier is likewise deliberately absent.
|
|
89
90
|
*
|
|
@@ -115,11 +116,12 @@ export interface ReadDenyBuiltinRow {
|
|
|
115
116
|
*/
|
|
116
117
|
export declare const READ_FACE_BUILTIN_DENY_TABLE: readonly ReadDenyBuiltinRow[];
|
|
117
118
|
/**
|
|
118
|
-
* The DEFAULT tier selection:
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
119
|
+
* The DEFAULT tier selection: EMPTY — an unconfigured deployment activates NO built-in row, so the
|
|
120
|
+
* table above judges nothing until a deployment selects tiers. Activation is a deployment decision
|
|
121
|
+
* with a visible seat, and the leg's wiring manifest reports which tiers it got.
|
|
122
|
+
*
|
|
123
|
+
* Still the NAMED default rather than nothing: `[]` and absence stay separately spellable on the
|
|
124
|
+
* seat, and a deployment profile wanting a different default changes THIS expression, not call sites.
|
|
123
125
|
*/
|
|
124
126
|
export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
|
|
125
127
|
/**
|
|
@@ -127,7 +129,8 @@ export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
|
|
|
127
129
|
* HandsToolkitOptions; deliberately NOT a TaskSpec key and NOT in the governed workflow whitelist,
|
|
128
130
|
* so no task or governed script can widen below its deployment).
|
|
129
131
|
* - `tiers` — EXACTLY-these tier selection: listed tiers are active, unlisted ones are not
|
|
130
|
-
* (`[]` = no built-in tiers at all — an explicit, legal posture; absent = {@link READ_DENY_DEFAULT_TIERS}
|
|
132
|
+
* (`[]` = no built-in tiers at all — an explicit, legal posture; absent = {@link READ_DENY_DEFAULT_TIERS},
|
|
133
|
+
* which is itself empty, so absent and `[]` resolve alike today and stay separately spellable).
|
|
131
134
|
* - `exclude` — per-row removal by canonical pattern name, applied AFTER tier selection.
|
|
132
135
|
* Unknown tier names / unknown row names refuse loudly — never silently ignored.
|
|
133
136
|
*/
|
|
@@ -142,11 +145,22 @@ export interface ReadDenyBuiltinConfig {
|
|
|
142
145
|
* table under a candidate configuration with the engine's own rules.
|
|
143
146
|
*/
|
|
144
147
|
export declare function resolveReadDenyBuiltins(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinRow[];
|
|
148
|
+
/**
|
|
149
|
+
* The tiers a deployment configuration ACTIVATES — the tier names of {@link resolveReadDenyBuiltins}'s
|
|
150
|
+
* surviving rows, in {@link READ_DENY_BUILTIN_TIERS} order (the closed set is the ordering source, so
|
|
151
|
+
* two deployments that select the same tiers in different spellings report the same list).
|
|
152
|
+
*
|
|
153
|
+
* MATERIAL, not intentional: a tier whose every row was removed by `exclude` activates nothing and
|
|
154
|
+
* is NOT reported. An EMPTY result means the built-in table contributes nothing on this leg — the
|
|
155
|
+
* unconfigured default. Same loud validation as the resolution it wraps.
|
|
156
|
+
*/
|
|
157
|
+
export declare function activeReadDenyBuiltinTiers(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinTier[];
|
|
145
158
|
/**
|
|
146
159
|
* READ_FACE_DEFAULT_DENY_ENTRIES — the DEFAULT-ACTIVE built-in set: {@link READ_FACE_BUILTIN_DENY_TABLE}
|
|
147
|
-
* under {@link READ_DENY_DEFAULT_TIERS}
|
|
148
|
-
*
|
|
149
|
-
*
|
|
160
|
+
* under {@link READ_DENY_DEFAULT_TIERS}. That selection is EMPTY, so this is an EMPTY list — the
|
|
161
|
+
* honest answer to what it is asked ("what does an unconfigured deployment refuse": nothing, from
|
|
162
|
+
* this table). Kept, and kept derived, so a consumer rendering that answer keeps reading it; the
|
|
163
|
+
* tier-annotated table is where a consumer renders "what COULD be switched on".
|
|
150
164
|
*/
|
|
151
165
|
export declare const READ_FACE_DEFAULT_DENY_ENTRIES: readonly ReadDenyEntry[];
|
|
152
166
|
/** A compiled segment-run pattern (shared engine — the write guard delegates here too). */
|
|
@@ -55,7 +55,7 @@ export const READ_FACE_BUILTIN_DENY_TABLE = [
|
|
|
55
55
|
{ pattern: ".aider.conf.yml", tier: "agent-config" },
|
|
56
56
|
{ pattern: ".gemini/settings.json", tier: "agent-config" },
|
|
57
57
|
];
|
|
58
|
-
export const READ_DENY_DEFAULT_TIERS =
|
|
58
|
+
export const READ_DENY_DEFAULT_TIERS = [];
|
|
59
59
|
export function resolveReadDenyBuiltins(config) {
|
|
60
60
|
let activeTiers;
|
|
61
61
|
if (config?.tiers === undefined) {
|
|
@@ -89,6 +89,10 @@ export function resolveReadDenyBuiltins(config) {
|
|
|
89
89
|
}
|
|
90
90
|
return READ_FACE_BUILTIN_DENY_TABLE.filter((r) => activeTiers.has(r.tier) && !excluded.has(r.pattern));
|
|
91
91
|
}
|
|
92
|
+
export function activeReadDenyBuiltinTiers(config) {
|
|
93
|
+
const rows = resolveReadDenyBuiltins(config);
|
|
94
|
+
return READ_DENY_BUILTIN_TIERS.filter((t) => rows.some((r) => r.tier === t));
|
|
95
|
+
}
|
|
92
96
|
export const READ_FACE_DEFAULT_DENY_ENTRIES = resolveReadDenyBuiltins().map((r) => r.pattern);
|
|
93
97
|
function asciiLower(s) {
|
|
94
98
|
let out = "";
|
|
@@ -77,6 +77,13 @@ export interface ReadEntry {
|
|
|
77
77
|
* disk (hash mismatch) always serves real content and the dedup disarms permanently.
|
|
78
78
|
* Optional/additive: absent = normal entry. */
|
|
79
79
|
seededFromContext?: boolean;
|
|
80
|
+
/** The id of the tool call whose RESULT carried the body this entry dedups against: when the
|
|
81
|
+
* request-layer stale-result pass clears THAT transmission, the entry retires with it
|
|
82
|
+
* (`applyContextShrinkToReadFileState`, `tool-results-cleared` arm). Recorded only by the Read
|
|
83
|
+
* lanes that TRANSMIT a body — a write-back's content is the model's own arguments and a
|
|
84
|
+
* seeded/re-attached entry never rode a tool result, so neither carries this and neither is
|
|
85
|
+
* retired by a clear. Optional/additive: absent (an older checkpoint) never matches a cleared id. */
|
|
86
|
+
transmittedByToolCallId?: string;
|
|
80
87
|
}
|
|
81
88
|
/** Per-task record of which files have been read, keyed by canonical path: owned by prepare-task,
|
|
82
89
|
* closure-captured by the tools, and serialized into the durable checkpoint. */
|
|
@@ -385,6 +392,36 @@ export declare function isShellRootedSpellingUnmapped(family: PathFamily | undef
|
|
|
385
392
|
* ⇒ `\\\\host\\share\\x`), for a lexical normalizer that recognises the backslash form only; every other spelling is
|
|
386
393
|
* returned as is. */
|
|
387
394
|
export declare function nativeUncSpellingOf(family: PathFamily | undefined, spelled: string): string;
|
|
395
|
+
/**
|
|
396
|
+
* Fold a TRAILING separator run off a spelling that is about to become a KEY: `<dir>/adir`, `<dir>/adir/`
|
|
397
|
+
* and `<dir>/adir//` name one directory and must mint one key. The existing-path arm folds them already,
|
|
398
|
+
* as a side effect of asking `realpath`; the arm that mints a key for a path that does NOT exist yet
|
|
399
|
+
* rejoined the model's own tail verbatim, so the same not-yet-created directory arrived under two and
|
|
400
|
+
* three keys — and a byte-wise reader (the per-file read state, the write gate's per-key ledger) treats
|
|
401
|
+
* two keys as two files.
|
|
402
|
+
*
|
|
403
|
+
* Called on the ANSWER, at the canonicalizer's single exit, and never on a spelling about to be probed.
|
|
404
|
+
* Two things break if it moves earlier. A trailing separator is an ASSERTION that the target is a
|
|
405
|
+
* directory and the platform enforces it (`<existing regular file>/` answers `not_directory`, reported
|
|
406
|
+
* here as a refusal), so folding before the probe spends that refusal and the slash-suffixed spelling of
|
|
407
|
+
* a file resolves to the file. And the ancestor walk chooses which ancestor to canonicalize by SPLITTING
|
|
408
|
+
* the spelling it was handed: a leaf stripped of its separator first is skipped as though it had been
|
|
409
|
+
* resolved, so `<root>/link/` — a link to a directory outside the root — comes back as the in-root
|
|
410
|
+
* spelling instead of the destination. Folding the answer costs neither.
|
|
411
|
+
*
|
|
412
|
+
* A spelling whose remainder is the bare ANCHOR keeps the separator the platform writes on a root — `/`,
|
|
413
|
+
* `C:\`, `\\host\share\`. That run IS the root's own spelling rather than a tail nobody wrote, and
|
|
414
|
+
* stripping it renames the spelling to nothing, to a DRIVE-RELATIVE location, or to a share root written
|
|
415
|
+
* unlike every other root. The question is asked in the module's own vocabulary ({@link pathIdentityOf} +
|
|
416
|
+
* {@link isIdentityDir}: is anything left BELOW the anchor?), so the three families need no arm each.
|
|
417
|
+
* Interior runs are NOT touched here: the ancestor walk folds them by probing, and a run in the middle of
|
|
418
|
+
* a name is the platform's to read.
|
|
419
|
+
*
|
|
420
|
+
* Scanned backwards rather than matched: `/[\\/]+$/` against a model-supplied spelling is quadratic when a
|
|
421
|
+
* long separator run precedes a non-separator (measured: ~5s for 32k leading slashes, on the caller's
|
|
422
|
+
* thread), and a path spelling is unbounded input.
|
|
423
|
+
*/
|
|
424
|
+
export declare function foldTrailingSeparators(spelled: string, family: PathFamily): string;
|
|
388
425
|
/**
|
|
389
426
|
* Join a RELATIVE-OR-ROOTED spelling to a base under the family, without folding (`..` across a symlinked
|
|
390
427
|
* parent is the env's to resolve). win = `path.win32` semantics: `/x`/`\x` lands on the base's drive or
|
|
@@ -541,12 +578,9 @@ export declare const PARTIAL_VIEW_READ_ESCAPE_HINT = "(Your last Read of this fi
|
|
|
541
578
|
* and partial-view shapes — leaves no trace the refusal renderer can stat for. It is therefore the
|
|
542
579
|
* renderer's FALLBACK hint (least specific, appended only when no sharper branch fired); appending it
|
|
543
580
|
* beside a sharper one would assert something false, since both of those shapes are files Read handled.
|
|
544
|
-
* Kept byte-identical to the sentence Write rendered inline before it moved here.
|
|
545
581
|
*
|
|
546
582
|
* The deadlock is a property of the READ GATE,
|
|
547
|
-
* not of Write — Edit and NotebookEdit hit the identical dead end
|
|
548
|
-
* with no way out named. All three now pass this one constant, which is why it is no longer named for
|
|
549
|
-
* the tool that happened to have it first: one sentence, three call sites, nothing to drift apart.
|
|
583
|
+
* not of Write — Edit and NotebookEdit hit the identical dead end.
|
|
550
584
|
*/
|
|
551
585
|
export declare const READ_REFUSED_ESCAPE_HINT = "(If the Read tool refuses this file (binary/unknown encoding), overwrite or convert it with bash instead \u2014 e.g. `rm` + rewrite, or `iconv`.)";
|
|
552
586
|
/**
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -371,11 +371,17 @@ function mintIdentity(anchor, segments, stripTail) {
|
|
|
371
371
|
out.pop();
|
|
372
372
|
continue;
|
|
373
373
|
}
|
|
374
|
-
const stripped = stripTail ? seg.
|
|
374
|
+
const stripped = stripTail ? seg.slice(0, trailingDotSpaceStart(seg)) : seg;
|
|
375
375
|
out.push(stripped.length > 0 ? stripped : seg);
|
|
376
376
|
}
|
|
377
377
|
return (out.length === 0 ? anchor || "/" : `${anchor}/${out.join("/")}`);
|
|
378
378
|
}
|
|
379
|
+
function trailingDotSpaceStart(seg) {
|
|
380
|
+
let end = seg.length;
|
|
381
|
+
while (end > 0 && (seg[end - 1] === "." || seg[end - 1] === " "))
|
|
382
|
+
end -= 1;
|
|
383
|
+
return end;
|
|
384
|
+
}
|
|
379
385
|
function identityAnchorOf(identity) {
|
|
380
386
|
return identity.startsWith("//") ? identity.split("/").slice(0, 4).join("/") : identity.slice(0, 2);
|
|
381
387
|
}
|
|
@@ -403,6 +409,18 @@ export function isShellRootedSpellingUnmapped(family, spelled) {
|
|
|
403
409
|
export function nativeUncSpellingOf(family, spelled) {
|
|
404
410
|
return family === "win" && isUncSpelling(spelled) ? `\\\\${spelled.slice(2).replace(/\//g, "\\")}` : spelled;
|
|
405
411
|
}
|
|
412
|
+
export function foldTrailingSeparators(spelled, family) {
|
|
413
|
+
let end = spelled.length;
|
|
414
|
+
while (end > 0 && (spelled[end - 1] === "/" || (family === "win" && spelled[end - 1] === "\\")))
|
|
415
|
+
end -= 1;
|
|
416
|
+
if (end === spelled.length)
|
|
417
|
+
return spelled;
|
|
418
|
+
const stripped = spelled.slice(0, end);
|
|
419
|
+
const identity = pathIdentityOf(stripped, family);
|
|
420
|
+
if (!identity.ok || !isIdentityDir(identity.identity, family))
|
|
421
|
+
return spelled;
|
|
422
|
+
return stripped;
|
|
423
|
+
}
|
|
406
424
|
export function joinForFamily(family, base, rel) {
|
|
407
425
|
if (family === "posix")
|
|
408
426
|
return `${base.replace(/\/+$/, "")}/${rel}`;
|
|
@@ -500,7 +518,8 @@ export async function canonicalizeTarget(env, path, signal, family, baseCwd) {
|
|
|
500
518
|
throw e;
|
|
501
519
|
});
|
|
502
520
|
try {
|
|
503
|
-
|
|
521
|
+
const answer = await withGateTimeout(probe, DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "pathCanonicalization");
|
|
522
|
+
return answer.ok ? { ...answer, key: foldTrailingSeparators(answer.key, family) } : answer;
|
|
504
523
|
}
|
|
505
524
|
catch (err) {
|
|
506
525
|
if (seenThrow && err === thrownByProbe)
|
|
@@ -546,7 +565,16 @@ async function probeRealTarget(env, path, target, family, signal) {
|
|
|
546
565
|
if (!link.ok) {
|
|
547
566
|
return { ok: false, message: `cannot read symlink "${path}": ${link.error.message}`, unresolvedSymlink: true };
|
|
548
567
|
}
|
|
549
|
-
const
|
|
568
|
+
const screenedTarget = win32NamespaceScreen(link.value, family);
|
|
569
|
+
if (!screenedTarget.ok) {
|
|
570
|
+
return {
|
|
571
|
+
ok: false,
|
|
572
|
+
message: `cannot resolve symlink "${path}": its target "${clipSpelling(link.value)}" ${WIN32_NAMESPACE_REFUSAL_TEXT[screenedTarget.refuse]}; refused (${screenedTarget.refuse}).`,
|
|
573
|
+
refused: screenedTarget.refuse,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
const hopSpelling = family === "win" ? screenedTarget.spelled : link.value;
|
|
577
|
+
const linkTarget = isAbsoluteForFamily(family, hopSpelling) ? hopSpelling : joinForFamily(family, parentDir(abs), hopSpelling);
|
|
550
578
|
const reabs = await env.absolutePath(linkTarget, signal);
|
|
551
579
|
if (!reabs.ok) {
|
|
552
580
|
return { ok: false, message: `cannot resolve symlink target "${linkTarget}" for "${path}": ${reabs.error.message}`, unresolvedSymlink: true };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.23.0",
|
|
4
4
|
"description": "Stateless, task-oriented AI agent core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && tsc -p tsconfig.build.json --emitDeclarationOnly --removeComments false",
|
|
51
|
-
"typecheck": "tsc --noEmit",
|
|
51
|
+
"typecheck": "tsc --noEmit && echo \"typecheck OK (tsc --noEmit clean)\"",
|
|
52
52
|
"pretest": "npm run gate:debts",
|
|
53
53
|
"test": "vitest run --exclude \"**/*.local.test.ts\"",
|
|
54
54
|
"test:watch": "vitest",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
|
|
3
3
|
"_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
|
|
4
|
-
"count":
|
|
4
|
+
"count": 2395,
|
|
5
5
|
"exports": {
|
|
6
6
|
"A2ATaskState": "type",
|
|
7
7
|
"A2ATaskStateReversal": "type",
|
|
@@ -712,6 +712,7 @@
|
|
|
712
712
|
"McpImageResizer": "type",
|
|
713
713
|
"McpInjectionDropFacts": "interface",
|
|
714
714
|
"McpInjectionDropReason": "type",
|
|
715
|
+
"McpReconnectResult": "interface",
|
|
715
716
|
"McpRefreshResult": "interface",
|
|
716
717
|
"McpServerSpec": "interface",
|
|
717
718
|
"McpServerStatus": "interface",
|
|
@@ -1061,6 +1062,8 @@
|
|
|
1061
1062
|
"RENDER_HINT_MAX_LIST": "variable",
|
|
1062
1063
|
"REPORT_FINDINGS_TOOL_NAME": "variable",
|
|
1063
1064
|
"RESOURCE_CHECKPOINT_VERSION": "variable",
|
|
1065
|
+
"RETENTION_CASCADE_DOMAINS": "variable",
|
|
1066
|
+
"RETENTION_CASCADE_EXEMPT": "variable",
|
|
1064
1067
|
"RETIRED_TOOL_NAMES": "variable",
|
|
1065
1068
|
"RETRYABLE_REMOTE_ERROR_CODES": "variable",
|
|
1066
1069
|
"ROLE_TIER_DEFAULTS": "variable",
|
|
@@ -1612,6 +1615,7 @@
|
|
|
1612
1615
|
"WriteProtectionMount": "interface",
|
|
1613
1616
|
"WriteProtectionTargetView": "type",
|
|
1614
1617
|
"WriteProtectionVerdict": "type",
|
|
1618
|
+
"WriteProtectionViewAbsence": "interface",
|
|
1615
1619
|
"WriteReceipt": "interface",
|
|
1616
1620
|
"XARGS_READ_ONLY_TARGETS": "variable",
|
|
1617
1621
|
"ackAdoptionConfig": "function",
|
|
@@ -1659,6 +1663,7 @@
|
|
|
1659
1663
|
"atomicWriteFile": "function",
|
|
1660
1664
|
"attachToolContract": "function",
|
|
1661
1665
|
"autoModeArmingRecipeOf": "function",
|
|
1666
|
+
"autoRunOnRecommendationEffective": "function",
|
|
1662
1667
|
"backgroundAgentStoreContract": "function",
|
|
1663
1668
|
"backgroundAgentStoreScopesContract": "function",
|
|
1664
1669
|
"bashReadBoundaryProbe": "function",
|
|
@@ -3104,6 +3109,7 @@
|
|
|
3104
3109
|
"McpImageResizer": "advanced",
|
|
3105
3110
|
"McpInjectionDropFacts": "advanced",
|
|
3106
3111
|
"McpInjectionDropReason": "advanced",
|
|
3112
|
+
"McpReconnectResult": "advanced",
|
|
3107
3113
|
"McpRefreshResult": "advanced",
|
|
3108
3114
|
"McpServerSpec": "advanced",
|
|
3109
3115
|
"McpServerStatus": "advanced",
|
|
@@ -3453,6 +3459,8 @@
|
|
|
3453
3459
|
"RENDER_HINT_MAX_LIST": "advanced",
|
|
3454
3460
|
"REPORT_FINDINGS_TOOL_NAME": "internal",
|
|
3455
3461
|
"RESOURCE_CHECKPOINT_VERSION": "advanced",
|
|
3462
|
+
"RETENTION_CASCADE_DOMAINS": "advanced",
|
|
3463
|
+
"RETENTION_CASCADE_EXEMPT": "advanced",
|
|
3456
3464
|
"RETIRED_TOOL_NAMES": "advanced",
|
|
3457
3465
|
"RETRYABLE_REMOTE_ERROR_CODES": "advanced",
|
|
3458
3466
|
"ROLE_TIER_DEFAULTS": "advanced",
|
|
@@ -4004,6 +4012,7 @@
|
|
|
4004
4012
|
"WriteProtectionMount": "advanced",
|
|
4005
4013
|
"WriteProtectionTargetView": "advanced",
|
|
4006
4014
|
"WriteProtectionVerdict": "advanced",
|
|
4015
|
+
"WriteProtectionViewAbsence": "advanced",
|
|
4007
4016
|
"WriteReceipt": "internal",
|
|
4008
4017
|
"XARGS_READ_ONLY_TARGETS": "advanced",
|
|
4009
4018
|
"ackAdoptionConfig": "advanced",
|
|
@@ -4051,6 +4060,7 @@
|
|
|
4051
4060
|
"atomicWriteFile": "advanced",
|
|
4052
4061
|
"attachToolContract": "advanced",
|
|
4053
4062
|
"autoModeArmingRecipeOf": "advanced",
|
|
4063
|
+
"autoRunOnRecommendationEffective": "internal",
|
|
4054
4064
|
"backgroundAgentStoreContract": "advanced",
|
|
4055
4065
|
"backgroundAgentStoreScopesContract": "advanced",
|
|
4056
4066
|
"bashReadBoundaryProbe": "advanced",
|