@sema-agent/core 7.23.3 → 7.23.5
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 +70 -0
- package/dist/agents/list-agents-tool.d.ts +5 -1
- package/dist/agents/list-agents-tool.js +14 -2
- package/dist/brain/anthropic.js +3 -2
- package/dist/brain/circuit-breaker.js +3 -3
- package/dist/brain/degrading.js +3 -5
- package/dist/brain/errors.d.ts +35 -18
- package/dist/brain/errors.js +49 -31
- package/dist/brain/failover.js +2 -3
- package/dist/brain/open-responses.js +6 -5
- package/dist/brain/openai.js +6 -5
- package/dist/brain/route-adjudicator.d.ts +26 -4
- package/dist/brain/route-adjudicator.js +26 -0
- package/dist/brain/stream-engine.js +8 -2
- package/dist/core/checkpoint-store.d.ts +4 -6
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/permission-rule-model.js +8 -5
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -0
- package/dist/core/runner/prepare-delegation-surface.js +1 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -1
- package/dist/core/runner/prepare-hands-readface.js +7 -7
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/run-leg.js +2 -13
- package/dist/core/task-spec.d.ts +2 -1
- package/dist/core/thrown-value.d.ts +30 -0
- package/dist/core/thrown-value.js +23 -1
- package/dist/core/tool-catalog-entries.js +1 -1
- package/dist/core/tool-model-gate.js +3 -15
- package/dist/core/tools.js +7 -7
- package/dist/core/untrusted-text.d.ts +6 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/core/workflow-run-store-contract.js +13 -0
- package/dist/core/workflow-run-store.d.ts +15 -0
- package/dist/core/workflow-run-store.js +1 -0
- package/dist/engine/loop/agent-loop.js +10 -24
- package/dist/tools/fs/bash-program-position.d.ts +23 -0
- package/dist/tools/fs/bash-program-position.js +11 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +64 -42
- package/dist/tools/fs/bash-readonly-classifier.js +33 -10
- package/dist/tools/fs/fs-bash.d.ts +41 -3
- package/dist/tools/fs/fs-bash.js +152 -58
- package/dist/tools/fs/index.d.ts +2 -1
- package/dist/tools/fs/index.js +1 -0
- package/dist/tools/fs/read-deny.d.ts +28 -0
- package/dist/tools/fs/read-deny.js +11 -0
- package/dist/tools/fs/safety.d.ts +1 -1
- package/dist/tools/fs/safety.js +2 -1
- package/dist/tools/monitor.d.ts +5 -0
- package/dist/tools/monitor.js +7 -0
- package/package.json +1 -1
- package/wire-changes/core@7.23.4.json +58 -0
- package/wire-changes/core@7.23.5.json +133 -0
|
@@ -159,6 +159,17 @@ export function argvReentryUnjudged(pos, acceptedTargets) {
|
|
|
159
159
|
return runs === undefined || !acceptedTargets.has(runs);
|
|
160
160
|
}
|
|
161
161
|
export const SHELL_SCAN_DECLINES = ["unreadable_program_position", "stream_fed_operands"];
|
|
162
|
+
export const BOUNDARY_SEAT_READS_DECLINE = {
|
|
163
|
+
unreadable_program_position: true,
|
|
164
|
+
stream_fed_operands: true,
|
|
165
|
+
};
|
|
166
|
+
export const CLASSIFY_SEAT_READS_DECLINE = {
|
|
167
|
+
unreadable_program_position: false,
|
|
168
|
+
stream_fed_operands: false,
|
|
169
|
+
};
|
|
170
|
+
export function seatDeclineEvidence(table) {
|
|
171
|
+
return new Set(SHELL_SCAN_DECLINES.filter((decline) => table[decline] === true));
|
|
172
|
+
}
|
|
162
173
|
export function programPositionRunsShell(pos, shellProgram) {
|
|
163
174
|
if (pos.refusal !== undefined)
|
|
164
175
|
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;
|
|
@@ -76,11 +76,13 @@ export interface LeadingCommandNameOptions {
|
|
|
76
76
|
* Read a shell operator that sits inside a QUOTED region as ordinary argument TEXT rather than as an
|
|
77
77
|
* operator, so `grep -E "a|b" f.log` parses as the single simple command it is.
|
|
78
78
|
*
|
|
79
|
-
* OPT-IN, and
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
79
|
+
* OPT-IN, and the two callers that set it share one premise: the whole command has ALREADY been refused
|
|
80
|
+
* for backslashes / an expanding `$` / backticks / subshells / line breaks, so they are inside
|
|
81
|
+
* {@link quoteMask}'s exactness envelope — the compound classify face
|
|
82
|
+
* ({@link classifyCompoundReadonlyDetailed}) and the persisted permission-RULE lane (`RULE_LANE_FLOOR`),
|
|
83
|
+
* whose segments come out of that same gate ({@link splitShellCompoundSegments}). The `bash_readonly`
|
|
84
|
+
* DECLARATION face does NOT set it: its contract is "a single simple command with no operator characters
|
|
85
|
+
* at all", and it runs on raw tool input with no whole-string gate in front of it.
|
|
84
86
|
*
|
|
85
87
|
* Fail-closed on unbalanced quoting: with a region left open, the mask is not trusted and every
|
|
86
88
|
* operator character counts, exactly as without this option.
|
|
@@ -272,28 +274,24 @@ export interface BashReadonlyRootBoundary {
|
|
|
272
274
|
* and demotes (with no out-of-root signal — the classifier does not know where it points, which is a
|
|
273
275
|
* different verdict from knowing it points outside). */
|
|
274
276
|
homeDir?: string;
|
|
275
|
-
/** The sensitive-path read deny judge over LEXICALLY RESOLVED operands: a hit
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* views (canonical + lexical); what this seat is and is not is stated once, at
|
|
294
|
-
* `shell.read_boundary.friction_floor`. The `bash_readonly` face deliberately does NOT wire this seat (its
|
|
295
|
-
* roots containment + command allowlist double gate is the deployment's own read-safety
|
|
296
|
-
* declaration — v1 ruling). */
|
|
277
|
+
/** The sensitive-path read deny judge over LEXICALLY RESOLVED operands: a hit demotes the command (ask,
|
|
278
|
+
* never auto-allow), independently of the roots — in-root operands are judged too. Returns the matched
|
|
279
|
+
* pattern, or null. TWO named residuals, both inherited from this classifier's declared purity
|
|
280
|
+
* (synchronous, zero I/O — the same scope stated for the containment half): ① operand TARGET matching
|
|
281
|
+
* only, no ancestor intersection: the judge sees the operand's own resolved spelling, never its subtree.
|
|
282
|
+
* The RECURSIVE-reach half of ① is narrowed — a listed recursive verb's path operand under this wired
|
|
283
|
+
* seat rides {@link CompoundReadonlyVerdict.recursiveReadPaths} (⊂ undecidedPaths), so `grep -r x ~/` no
|
|
284
|
+
* longer auto-allows; what REMAINS is the form table's stated open set (the KNOWN OPEN SET note at
|
|
285
|
+
* {@link RECURSIVE_READ_FORMS}: a traversal verb absent there that a deployment allowlists classifies by
|
|
286
|
+
* its ordinary form and can auto-run a traversal unjudged). ② LEXICAL only HERE — an in-root symlink
|
|
287
|
+
* whose target is guarded reads as its innocent spelling at this seat, as it does for the containment
|
|
288
|
+
* half, because the classify auto-allow lane has no I/O seat by contract; the CANONICAL view is judged by
|
|
289
|
+
* an execute-time recheck that asks THIS judge (the same function object, forwarded) where each candidate
|
|
290
|
+
* really lands, on a `checkedPaths` literal and a pattern's expansions alike.
|
|
291
|
+
* EVERY read face wires this one seat, the `bash_readonly` shell included, at BOTH of its judging
|
|
292
|
+
* moments — roots answer WHERE a command may read and the allowlist WHICH verb runs, neither answers
|
|
293
|
+
* WHICH FILES are off the table. What this seat is and is not is stated once, at
|
|
294
|
+
* `shell.read_boundary.friction_floor`. */
|
|
297
295
|
denyMatch?: (resolvedPath: string) => string | null;
|
|
298
296
|
/** What the wired {@link denyMatch} judge would apply — the ACTIVE deny patterns. Read for ONE question:
|
|
299
297
|
* is there anything for a traversal to defeat? EMPTY ⇒ the judge refuses nothing, so a walk under it
|
|
@@ -333,6 +331,10 @@ export interface CompoundReadonlyVerdict {
|
|
|
333
331
|
* reads it to mint `mandated` — a stored allow rule or the read-only shell arm may retire a classify-
|
|
334
332
|
* tier question, never one the read face itself asked. Structural sibling of `outOfRootRead`. */
|
|
335
333
|
readDenied?: true;
|
|
334
|
+
/** The matched operand (resolved spelling) and the pattern — present iff {@link readDenied} (the
|
|
335
|
+
* `outOfRootRead`/`outOfRootPaths` idiom), so a refusal CARD names the target without parsing prose. */
|
|
336
|
+
readDeniedPath?: string;
|
|
337
|
+
readDeniedPattern?: string;
|
|
336
338
|
/**
|
|
337
339
|
* The paths this scan resolved INSIDE the roots (de-duplicated, first-seen order), i.e. the
|
|
338
340
|
* candidates a caller holding a filesystem should re-check with symlinks resolved. Absent when there
|
|
@@ -419,20 +421,19 @@ export interface CompoundReadonlyVerdict {
|
|
|
419
421
|
/**
|
|
420
422
|
* Operands of a RECURSIVE/EXPANDING read form (`grep -r`, `ls -R`, `du`, … — see
|
|
421
423
|
* {@link RECURSIVE_READ_FORMS}) judged with a {@link BashReadonlyRootBoundary.denyMatch} seat wired.
|
|
422
|
-
* The
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
424
|
+
* The judge sees only the operand's own resolved spelling, but a recursive verb reads the operand's whole
|
|
425
|
+
* SUBTREE — `grep -r x /home/user` touches `/home/user/.ssh/*` while the judged spelling matches nothing.
|
|
426
|
+
* The traversal's reach set is therefore not covered by the lexical check at all, and with zero I/O
|
|
427
|
+
* "provably not a directory" does not exist — so every such operand is UNDECIDED (no directory guessing,
|
|
428
|
+
* no bounded pre-check: both would be a false "resolved, inside" of exactly the kind
|
|
429
|
+
* {@link undecidedPaths} exists to prevent).
|
|
428
430
|
*
|
|
429
|
-
* Subset of {@link undecidedPaths} (same consumer contract: ask, never auto-allow); carried
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
* path is unchanged.
|
|
431
|
+
* Subset of {@link undecidedPaths} (same consumer contract: ask, never auto-allow); carried separately so
|
|
432
|
+
* a consumer minting prose can name the recursive-reach cause rather than the glob one. Minted ONLY when
|
|
433
|
+
* `denyMatch` is wired: without a judge there is nothing the traversal bypasses. EVERY face wiring the
|
|
434
|
+
* seat sees this field, the `bash_readonly` shell included, and answers it by its OWN rule for a demotion
|
|
435
|
+
* — that face has no approval channel, so what it cannot establish it REFUSES (an unbounded traversal is
|
|
436
|
+
* the same "the read set is not in the text" finding as a pattern the expander cannot enumerate).
|
|
436
437
|
*/
|
|
437
438
|
recursiveReadPaths?: readonly string[];
|
|
438
439
|
/**
|
|
@@ -618,8 +619,29 @@ export interface OutOfRootReadGateVerdict {
|
|
|
618
619
|
readonly paths: readonly string[];
|
|
619
620
|
/** Did the deployment's DENY judge speak about an operand this command names? The other half of the same
|
|
620
621
|
* question, over the very same readers, in a second pass WITH the deny judge — which is what lets the
|
|
621
|
-
* containment answer above stay deny-free and keep its `paths` contract. Absent ⇔ it said nothing.
|
|
622
|
-
|
|
622
|
+
* containment answer above stay deny-free and keep its `paths` contract. Absent ⇔ it said nothing.
|
|
623
|
+
*
|
|
624
|
+
* It carries the FINDING, not a boolean: this is the arm a command the grammar gate refused WHOLE
|
|
625
|
+
* (`cat .env > out.txt`, a subshell, a substitution) reaches, and there the compound verdict carries no
|
|
626
|
+
* deny fields at all — so a caller disclosing the hit has nowhere else to read the path and the pattern
|
|
627
|
+
* from, and said nothing. The second pass already computed both; throwing them away was the whole bug. */
|
|
628
|
+
readonly denied?: {
|
|
629
|
+
readonly path: string;
|
|
630
|
+
readonly pattern: string;
|
|
631
|
+
};
|
|
632
|
+
/** The reader operands this pass RESOLVED and placed inside the roots, plus the expansions it parsed but
|
|
633
|
+
* cannot enumerate — the candidates an execute-time recheck must canonicalize.
|
|
634
|
+
*
|
|
635
|
+
* Published because this pass reads SHAPES the compound walk hands nothing out for: a launcher whose
|
|
636
|
+
* program it cannot name (`env -- cat f`, `sudo -u root cat f`), a stream-fed one (`… | xargs cat f`).
|
|
637
|
+
* There the compound verdict carries no candidates at all, so the recheck had nothing to resolve and an
|
|
638
|
+
* in-root symlink to a deny-listed target RAN after an approval — while the same read spelled without
|
|
639
|
+
* the launcher was refused. Same operands, same judge, one more pass allowed to hand them over.
|
|
640
|
+
*
|
|
641
|
+
* NOT the out-of-root ones: those ride {@link paths}, they are what the boundary RAISED, and a leg that
|
|
642
|
+
* re-refused them would be a second adjudicator of a decision already made. */
|
|
643
|
+
readonly checked: readonly string[];
|
|
644
|
+
readonly undecided: readonly string[];
|
|
623
645
|
}
|
|
624
646
|
/** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
|
|
625
647
|
* reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sensitivePathDenyList } from "./read-deny.js";
|
|
1
2
|
import { hasWin32AliasedComponent, isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf, withinAnyRoot } from "./safety.js";
|
|
2
3
|
import { lexShellText, literalTildeSpelling, quoteMask, substitutionsInHeredocBody, SUBSTITUTION_PLACEHOLDER, wordsOf, } from "./bash-lexer.js";
|
|
3
4
|
import { ASSIGNMENT_WORD, COMMAND_LAUNCHERS, NOT_AUTO_ALLOWED, SHELL_BLOCK_WORDS, argvReentryUnjudged, isBareControlWord, programPositionRunsShell, shellScanDeclineOf, programBasename, programPositionRefusalSentence, readProgramPosition, } from "./bash-program-position.js";
|
|
@@ -19,11 +20,16 @@ function hasBareShellOperator(s, quotedOperatorsAreText) {
|
|
|
19
20
|
if (!mask.balanced)
|
|
20
21
|
return SHELL_OPERATORS.test(s);
|
|
21
22
|
for (let i = 0; i < s.length; i++) {
|
|
22
|
-
|
|
23
|
+
const ch = s[i];
|
|
24
|
+
if (!SHELL_OPERATORS.test(ch))
|
|
25
|
+
continue;
|
|
26
|
+
const literal = SHELL_EXPANDS_IN_DOUBLE_QUOTES.test(ch) ? mask.openedBy[i] === "'" : mask.quoted[i];
|
|
27
|
+
if (!literal)
|
|
23
28
|
return true;
|
|
24
29
|
}
|
|
25
30
|
return false;
|
|
26
31
|
}
|
|
32
|
+
const SHELL_EXPANDS_IN_DOUBLE_QUOTES = /[$`]/;
|
|
27
33
|
export function parseLeadingCommandName(command, options) {
|
|
28
34
|
const trimmed = command.trim();
|
|
29
35
|
if (!trimmed)
|
|
@@ -2169,8 +2175,10 @@ function evaluateReadBoundary(foldedSegments, boundary, mintGrantCandidate, reac
|
|
|
2169
2175
|
const denied = boundary.denyMatch?.(finding.path);
|
|
2170
2176
|
if (denied != null) {
|
|
2171
2177
|
return {
|
|
2172
|
-
reason: `a command operand resolves to "${finding.path}", which matches
|
|
2178
|
+
reason: `a command operand resolves to "${finding.path}", which matches ${sensitivePathDenyList(denied)} ${NOT_AUTO_ALLOWED}`,
|
|
2173
2179
|
readDenied: true,
|
|
2180
|
+
readDeniedPath: finding.path,
|
|
2181
|
+
readDeniedPattern: denied,
|
|
2174
2182
|
};
|
|
2175
2183
|
}
|
|
2176
2184
|
if (finding.kind === "inside") {
|
|
@@ -2299,7 +2307,7 @@ export function classifySimpleCommandReadBoundary(command, boundary) {
|
|
|
2299
2307
|
}
|
|
2300
2308
|
return evaluateReadBoundary([toks], boundary, true);
|
|
2301
2309
|
}
|
|
2302
|
-
const NOT_GATED = { gated: false, paths: [] };
|
|
2310
|
+
const NOT_GATED = { gated: false, paths: [], checked: [], undecided: [] };
|
|
2303
2311
|
const UNREADABLE_WHITELIST_WORD = /[$`()*?[\]{}]/;
|
|
2304
2312
|
const UNMODELLED_BASE_MOVERS = new Set(["pushd", "popd"]);
|
|
2305
2313
|
export const COMMAND_LAUNCHERS_FOR_TEST = COMMAND_LAUNCHERS;
|
|
@@ -2357,18 +2365,33 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
|
|
|
2357
2365
|
if (readers.length === 0)
|
|
2358
2366
|
return NOT_GATED;
|
|
2359
2367
|
if (baseMoved)
|
|
2360
|
-
return { gated: true, paths: [] };
|
|
2368
|
+
return { gated: true, paths: [], checked: [], undecided: [] };
|
|
2361
2369
|
const verdict = evaluateReadBoundary(readers, containmentOnly, false, readerReach);
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2370
|
+
const denyPass = boundary.denyMatch === undefined ? undefined : evaluateReadBoundary(readers, boundary, false, readerReach);
|
|
2371
|
+
const denied = denyPass?.readDenied === true && denyPass.readDeniedPath !== undefined && denyPass.readDeniedPattern !== undefined
|
|
2372
|
+
? { path: denyPass.readDeniedPath, pattern: denyPass.readDeniedPattern }
|
|
2373
|
+
: undefined;
|
|
2374
|
+
const resolvedOperands = {
|
|
2375
|
+
checked: [...(verdict.checkedPaths ?? []), ...(verdict.checkedSpellings ?? [])],
|
|
2376
|
+
undecided: [...(verdict.undecidedPaths ?? []), ...(verdict.undecidedSpellings ?? [])],
|
|
2377
|
+
};
|
|
2378
|
+
if (verdict.outOfRootRead === true) {
|
|
2379
|
+
return { gated: true, paths: verdict.outOfRootPaths ?? [], ...resolvedOperands, ...(denied !== undefined ? { denied } : {}) };
|
|
2380
|
+
}
|
|
2381
|
+
if (denied !== undefined)
|
|
2382
|
+
return { gated: false, paths: [], ...resolvedOperands, denied };
|
|
2383
|
+
return resolvedOperands.checked.length === 0 && resolvedOperands.undecided.length === 0 ? NOT_GATED : { gated: false, paths: [], ...resolvedOperands };
|
|
2366
2384
|
};
|
|
2367
2385
|
const direct = judge(command);
|
|
2368
|
-
if (direct.gated || direct.denied
|
|
2386
|
+
if (direct.gated || direct.denied !== undefined)
|
|
2369
2387
|
return direct;
|
|
2370
2388
|
const shape = POLL_LOOP_SHAPE.exec(command.trim());
|
|
2371
|
-
|
|
2389
|
+
if (shape === null)
|
|
2390
|
+
return direct;
|
|
2391
|
+
const loop = judge(shape[3]);
|
|
2392
|
+
return loop.gated || loop.denied !== undefined
|
|
2393
|
+
? loop
|
|
2394
|
+
: { ...loop, checked: [...direct.checked, ...loop.checked], undecided: [...direct.undecided, ...loop.undecided] };
|
|
2372
2395
|
}
|
|
2373
2396
|
export function classifyCompoundReadonly(command, allow, boundary) {
|
|
2374
2397
|
return classifyCompoundReadonlyDetailed(command, allow, boundary).reason;
|
|
@@ -4,6 +4,7 @@ import { type CwdRef } from "./fs-shared.js";
|
|
|
4
4
|
import type { ReversibilityVerdict } from "../../core/types.js";
|
|
5
5
|
import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
|
|
6
6
|
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
7
|
+
import { type ReadDenyMatcher } from "./read-deny.js";
|
|
7
8
|
/**
|
|
8
9
|
* A parsed-command classifier for the `bash` tool, exposed as a
|
|
9
10
|
* `ToolSpec.reversibilityProbe`. A full shell is treated as egress+irreversible by DEFAULT; this probe is the
|
|
@@ -65,6 +66,10 @@ export declare function bashReversibilityProbe(allow?: readonly string[], bounda
|
|
|
65
66
|
* ONE rule, TWO TABLES: which {@link ShellScanDecline} a SEAT reads as read evidence. A decline names no word,
|
|
66
67
|
* so it is never the boundary DECLARING anything about a path.
|
|
67
68
|
*
|
|
69
|
+
* BOTH tables are ROW-PER-MEMBER objects behind a compile-time fence ({@link BOUNDARY_SEAT_READS_DECLINE} /
|
|
70
|
+
* {@link CLASSIFY_SEAT_READS_DECLINE}); the sets below are DERIVED from those rows and the ARGUMENT for the
|
|
71
|
+
* rows is this block. "Empty by the argument" is a row saying `false`, not a literal nobody can judge.
|
|
72
|
+
*
|
|
68
73
|
* The READ-BOUNDARY seat is what the default/off doctrine and a bypass mode mount ALONE: there "not mandated"
|
|
69
74
|
* is VOUCHED, so a decline must count as evidence or a read nobody judged runs silently (`<out-of-root> |
|
|
70
75
|
* xargs cat` at zero asks — a closed P0). It keeps both. The CLASSIFY seat composes the same boundary with the
|
|
@@ -86,8 +91,13 @@ export declare function bashReversibilityProbe(allow?: readonly string[], bounda
|
|
|
86
91
|
* DID read naming a path outside the roots still gates the call ({@link classifyOutOfRootReadGate}).
|
|
87
92
|
*/
|
|
88
93
|
export declare const BOUNDARY_SEAT_DECLINE_EVIDENCE: ReadonlySet<ShellScanDecline>;
|
|
89
|
-
/** The classify doctrine's table — see above; empty
|
|
94
|
+
/** The classify doctrine's table in the shape its readers take — see above; empty because every row says `false`. */
|
|
90
95
|
export declare const CLASSIFY_SEAT_DECLINE_EVIDENCE: ReadonlySet<ShellScanDecline>;
|
|
96
|
+
export declare function fitMiddle(s: string, budget: number): string;
|
|
97
|
+
/** `s` shortened to `budget` by removing its MIDDLE, marked — cut through the repo's surrogate-safe
|
|
98
|
+
* primitive, never a raw `slice` (a raw cut leaves half an astral code point on a card and in a durable
|
|
99
|
+
* row). Under a dozen characters the HEAD is taken. */
|
|
100
|
+
export declare function elideMiddle(s: string, budget: number): string;
|
|
91
101
|
/**
|
|
92
102
|
* The READ-BOUNDARY seat of the full shell: a `ToolSpec.reversibilityProbe` that answers exactly ONE
|
|
93
103
|
* question — does the deployment's read boundary object to where this command reads? — and nothing about
|
|
@@ -291,6 +301,26 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
|
|
|
291
301
|
* Absent ⇒ historic bare tag. */
|
|
292
302
|
reminderMark?: string;
|
|
293
303
|
}): AgentTool;
|
|
304
|
+
type ShellCanonicalRefusal = {
|
|
305
|
+
content: string;
|
|
306
|
+
isError: true;
|
|
307
|
+
details?: unknown;
|
|
308
|
+
};
|
|
309
|
+
/** What a face contributes to the shared refusal cards: the TOOL refusing, and the three prose slots. */
|
|
310
|
+
export interface ShellFaceProse {
|
|
311
|
+
readonly tool: string;
|
|
312
|
+
readonly subject: string;
|
|
313
|
+
readonly verdictPhrase: string;
|
|
314
|
+
readonly note: string;
|
|
315
|
+
}
|
|
316
|
+
export type ShellCanonicalRecheck = (command: string, signal?: AbortSignal) => Promise<ShellCanonicalRefusal | undefined>;
|
|
317
|
+
/**
|
|
318
|
+
* THE execute-time canonical recheck, mounted by every face that spawns a shell command — the full `Bash`
|
|
319
|
+
* tool and `Monitor`, whose approval-time seats judge the WRITTEN spelling while this re-judges where the
|
|
320
|
+
* kernel takes those words. The SAME leg, not a second one; the contract below is the leg's own. */
|
|
321
|
+
export declare function createShellCanonicalRecheck(env: ExecutionEnv, readBoundary: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined) | undefined, face?: ShellFaceProse): ShellCanonicalRecheck;
|
|
322
|
+
/** The `Monitor` face's prose — the full shell's with its own tool name: one leg, one finding, two tools. */
|
|
323
|
+
export declare const MONITOR_ESCAPE_PROSE: ShellFaceProse;
|
|
294
324
|
/**
|
|
295
325
|
* `bash_readonly` (effect:read) — a restricted shell for the verifier read-only boundary: a single
|
|
296
326
|
* allowlisted, bare command with no shell operators. effect:read lets it survive the verifier's
|
|
@@ -306,12 +336,17 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
|
|
|
306
336
|
* read-only band), so an out-of-root read here is not something to escalate, it is outside what the tool
|
|
307
337
|
* is. It is refused, fail-closed, before anything executes — including the conservative direction, where
|
|
308
338
|
* the operand cannot be resolved statically (there is nothing to degrade to).
|
|
339
|
+
*
|
|
340
|
+
* The boundary has TWO halves and this face wires both: roots answer WHERE a command may read and the
|
|
341
|
+
* allowlist WHICH verb runs, neither answers WHICH FILES are off the table (a deny row holds INSIDE the
|
|
342
|
+
* roots). The judge is the structured read faces' own, asked at both judging moments; every finding lands
|
|
343
|
+
* on the standing rule above — a demotion here is a refusal.
|
|
309
344
|
*/
|
|
310
345
|
export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, opts?: {
|
|
311
346
|
/** Same timeout-cap overrides the full `bash` leg takes — see
|
|
312
347
|
* {@link import("./index.js").HandsToolkitOptions.bashDefaultTimeoutMs} /
|
|
313
|
-
* {@link import("./index.js").HandsToolkitOptions.bashMaxTimeoutMs}
|
|
314
|
-
*
|
|
348
|
+
* {@link import("./index.js").HandsToolkitOptions.bashMaxTimeoutMs} — both foreground legs share
|
|
349
|
+
* runShell's clamp, so both must share the configurable resolution. */
|
|
315
350
|
bashDefaultTimeoutMs?: number;
|
|
316
351
|
bashMaxTimeoutMs?: number;
|
|
317
352
|
/** Structured sink for the timeout-cap discard announcements — same seat the full `bash` leg
|
|
@@ -321,6 +356,9 @@ export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical:
|
|
|
321
356
|
* canonical). A read this deployment already sanctions for Read/Grep/Glob is equally sanctioned
|
|
322
357
|
* here — the two faces must not disagree about which directories exist for this session. */
|
|
323
358
|
additionalRoots?: readonly string[];
|
|
359
|
+
/** The deployment's compiled read deny set — the SAME matcher object the structured read faces of this
|
|
360
|
+
* mount hold (one compile, one table). Absent ⇒ no deny judge, and both judging moments are unchanged. */
|
|
361
|
+
readDeny?: ReadDenyMatcher;
|
|
324
362
|
/** The run's tool-progress sink — the SAME seat the full shell leg takes, for the same reason its
|
|
325
363
|
* two neighbours above are shared: both foreground legs run through `runShell`, so a long command
|
|
326
364
|
* must breathe on both or a person watching one of them is told nothing. This face has no detach
|