@sema-agent/core 7.29.0 → 7.30.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 +361 -0
- package/dist/agents/child-model-seat.d.ts +59 -7
- package/dist/agents/child-model-seat.js +33 -7
- package/dist/agents/cross-session-judge.d.ts +27 -5
- package/dist/agents/cross-session-judge.js +23 -12
- package/dist/agents/list-agents-tool.d.ts +31 -6
- package/dist/agents/list-agents-tool.js +66 -14
- package/dist/agents/peer-admission.d.ts +20 -8
- package/dist/agents/peer-admission.js +21 -12
- package/dist/agents/peer-directory.d.ts +29 -0
- package/dist/agents/peer-directory.js +20 -0
- package/dist/agents/peer-held-queue.d.ts +31 -4
- package/dist/agents/peer-held-queue.js +32 -1
- package/dist/agents/peer-session-drain.d.ts +91 -13
- package/dist/agents/peer-session-drain.js +409 -102
- package/dist/agents/read-inbox-tool.d.ts +22 -0
- package/dist/agents/read-inbox-tool.js +39 -0
- package/dist/agents/roster-store.d.ts +2 -4
- package/dist/agents/roster-store.js +1 -1
- package/dist/agents/send-message-tool.d.ts +4 -4
- package/dist/agents/send-message-tool.js +22 -2
- package/dist/agents/subagent.js +62 -44
- package/dist/agents/team-role.d.ts +34 -0
- package/dist/agents/team-role.js +17 -0
- package/dist/agents/verify.js +2 -2
- package/dist/core/auto-compaction.d.ts +42 -0
- package/dist/core/auto-compaction.js +35 -2
- package/dist/core/child-clamp-seats.d.ts +11 -0
- package/dist/core/child-clamp-seats.js +3 -0
- package/dist/core/context-edit.d.ts +3 -2
- package/dist/core/context-edit.js +6 -5
- package/dist/core/context-guard.d.ts +2 -1
- package/dist/core/context-guard.js +3 -2
- package/dist/core/context-settings.d.ts +103 -0
- package/dist/core/context-settings.js +60 -0
- package/dist/core/engine-notice.d.ts +11 -1
- package/dist/core/frame-settlement.d.ts +97 -0
- package/dist/core/frame-settlement.js +36 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -0
- package/dist/core/hooks.d.ts +10 -10
- package/dist/core/mailbox-store.d.ts +41 -7
- package/dist/core/mailbox-store.js +9 -3
- package/dist/core/parent-spec-seat.d.ts +14 -1
- package/dist/core/parent-spec-seat.js +6 -0
- package/dist/core/permission-rule-model.js +1 -1
- package/dist/core/roles.d.ts +20 -0
- package/dist/core/roles.js +35 -16
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/compaction-call-options.d.ts +26 -2
- package/dist/core/runner/compaction-call-options.js +9 -0
- package/dist/core/runner/compaction-seams.d.ts +4 -3
- package/dist/core/runner/compaction-seams.js +13 -6
- package/dist/core/runner/contracts.d.ts +56 -7
- package/dist/core/runner/decide-continuation.d.ts +2 -1
- package/dist/core/runner/decide-continuation.js +11 -10
- package/dist/core/runner/initial-run-state.js +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
- package/dist/core/runner/prepare-config-doors.js +4 -1
- package/dist/core/runner/prepare-context-lane.d.ts +9 -2
- package/dist/core/runner/prepare-context-lane.js +15 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +9 -3
- package/dist/core/runner/prepare-file-history.js +10 -8
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -1
- package/dist/core/runner/prepare-task.js +22 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
- package/dist/core/runner/prepare-wiring-manifest.js +6 -1
- package/dist/core/runner/retired-runner-deps.d.ts +3 -1
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
- package/dist/core/runner/run-compaction-machinery.js +5 -3
- package/dist/core/runner/run-harness-handlers.js +1 -1
- package/dist/core/runner/run-identity-wiring.js +19 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +8 -2
- package/dist/core/runner/run-recovery-lanes.js +9 -5
- package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
- package/dist/core/runner/run-settle-and-teardown.js +6 -10
- package/dist/core/runner/run-stop-and-final-verify.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +32 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +32 -1
- package/dist/core/runner-deps.d.ts +44 -13
- package/dist/core/swappable-deps.d.ts +20 -4
- package/dist/core/swappable-deps.js +6 -1
- package/dist/core/task-event.d.ts +5 -0
- package/dist/core/task-notification.d.ts +144 -2
- package/dist/core/task-notification.js +154 -5
- package/dist/core/task-result.d.ts +14 -0
- package/dist/core/task-spec.d.ts +7 -0
- package/dist/core/tool-catalog-entries.js +2 -1
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-policy.d.ts +9 -4
- package/dist/core/tool-spec.d.ts +14 -0
- package/dist/core/wiring-manifest.d.ts +12 -0
- package/dist/core/wiring-manifest.js +5 -1
- package/dist/engine/harness/agent-harness.d.ts +14 -1
- package/dist/engine/harness/agent-harness.js +28 -3
- package/dist/engine/harness/types.d.ts +3 -1
- package/dist/engine/llm/types.d.ts +8 -7
- package/dist/engine/llm/types.js +1 -1
- package/dist/engine/session/import-validate.js +4 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +7 -5
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/orchestration/workflow.js +8 -5
- package/dist/shell/lex.d.ts +30 -51
- package/dist/shell/lex.js +237 -284
- package/dist/stores/cc/mailbox-store.js +3 -2
- package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
- package/dist/stores/file/file-history-store.d.ts +2 -4
- package/dist/stores/file/file-history-store.js +1 -1
- package/dist/stores/file/fs-atomic.d.ts +16 -11
- package/dist/stores/file/fs-atomic.js +17 -6
- package/dist/stores/file/index.d.ts +5 -6
- package/dist/stores/file/index.js +1 -1
- package/dist/stores/file/mailbox-store.d.ts +4 -9
- package/dist/stores/file/mailbox-store.js +29 -30
- package/dist/stores/file/session-policy-store.d.ts +2 -0
- package/dist/stores/file/session-policy-store.js +1 -1
- package/dist/stores/file/session-store.d.ts +11 -7
- package/dist/stores/file/session-store.js +23 -16
- package/dist/stores/file/shared-ledger.js +3 -14
- package/dist/stores/file/tool-result-store.d.ts +2 -0
- package/dist/stores/file/tool-result-store.js +5 -2
- package/dist/stores/file/workflow-journal-store.js +14 -9
- package/dist/tools/fs/bash-program-position.d.ts +15 -24
- package/dist/tools/fs/bash-program-position.js +11 -29
- package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
- package/dist/tools/fs/bash-readonly-classifier.js +400 -272
- package/dist/tools/fs/shell-read-check.js +3 -3
- package/dist/tools/fs/shell-read-establish.js +1 -1
- package/dist/tools/fs/shell-read-plan.d.ts +11 -6
- package/dist/tools/fs/shell-read-plan.js +167 -183
- package/dist/tools/fs/shell-read-word.d.ts +50 -0
- package/dist/tools/fs/shell-read-word.js +59 -0
- package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
- package/dist/tools/fs/shell-reentry-text.js +73 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +58 -8
- package/wire-changes/core@7.30.0.json +1821 -0
|
@@ -8,27 +8,22 @@
|
|
|
8
8
|
* readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader
|
|
9
9
|
* does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
|
|
10
10
|
*/
|
|
11
|
-
import { SHELL_CONTROL_PREFIXES, type
|
|
11
|
+
import { SHELL_CONTROL_PREFIXES, type LexedWord } from "../../shell/lex.js";
|
|
12
12
|
/** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
|
|
13
13
|
* they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
|
|
14
14
|
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
|
|
15
15
|
/** argv[0] as the program bash will resolve it: `/usr/bin/cat` and `cat` are one name. */
|
|
16
16
|
export declare function programBasename(word: string): string;
|
|
17
17
|
/**
|
|
18
|
-
* The `NAME=` / `NAME+=` prefix
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* The `NAME=` / `NAME+=` prefix as WRITTEN in a whitespace-split token. This is NOT the position reader's grammar
|
|
19
|
+
* — the reader reads the lexer's {@link LexedWord.assignment} fact — but the two readers that still walk the
|
|
20
|
+
* command TEXT rather than its words (the unverifiable-delete policy and the activity summary) read it, and
|
|
21
|
+
* they read it here so that they read one spelling. It retires with them.
|
|
22
22
|
*/
|
|
23
23
|
export declare const ASSIGNMENT_PREFIX: RegExp;
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `name` is the VARIABLE the word assigns to, which is the part before any subscript: `A[0]=x` writes one
|
|
28
|
-
* element of `A`, so a reader asking "is `$A` settled" must not be told that `A[0]` is. `settlesWholeName` is
|
|
29
|
-
* false for exactly the two spellings that do not settle it — an append (the earlier content is not in the
|
|
30
|
-
* text) and a subscript (the other elements are not) — so a consumer of that question asks it once.
|
|
31
|
-
*/
|
|
24
|
+
/** The prefix a whitespace-split token BEGINS with, or `undefined` — the text readers' view of
|
|
25
|
+
* {@link ASSIGNMENT_PREFIX}; `name` is the variable before any subscript, `settlesWholeName` false for an
|
|
26
|
+
* append and for a subscript. */
|
|
32
27
|
export declare function writtenAssignmentPrefix(token: string): {
|
|
33
28
|
readonly prefix: string;
|
|
34
29
|
readonly name: string;
|
|
@@ -40,18 +35,14 @@ export { SHELL_CONTROL_PREFIXES };
|
|
|
40
35
|
export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
|
|
41
36
|
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
|
|
42
37
|
* looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
|
|
43
|
-
export declare function isBareControlWord(
|
|
38
|
+
export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean;
|
|
44
39
|
/**
|
|
45
|
-
* A word bash strips as an ASSIGNMENT before it chooses the program
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* `NAME+=` makes the whole word a program name looked up on PATH (`'A=x'`, `'A+=x'`, `A\+=x`), while quoting the
|
|
50
|
-
* VALUE does not (`A+=".env"` assigns `.env` exactly as `A+=.env` does — verified against the shell). A
|
|
51
|
-
* whole-word test conflates the two and reads the quoted VALUE as a program name; a FOLDED-word test conflates
|
|
52
|
-
* them the other way and reads the quoted PROGRAM NAME as an assignment, stepping over the real program.
|
|
40
|
+
* A word bash strips as an ASSIGNMENT before it chooses the program: the lexer's assignment fact, written BARE
|
|
41
|
+
* (the name, the brackets and the operator unquoted — a quoted VALUE still assigns, a quoted operator is a
|
|
42
|
+
* program name), read where bash accepts one (the lexer's own command-position state, which this reader's walk
|
|
43
|
+
* over control words and earlier assignments re-traces). One fact, two readers that must agree.
|
|
53
44
|
*/
|
|
54
|
-
export declare function isLeadingAssignmentWord(
|
|
45
|
+
export declare function isLeadingAssignmentWord(words: readonly LexedWord[], i: number): boolean;
|
|
55
46
|
/**
|
|
56
47
|
* Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
|
|
57
48
|
*
|
|
@@ -189,7 +180,7 @@ export interface ProgramPosition {
|
|
|
189
180
|
* WRITTEN word (judged on its basename, as the program it names) refuses the same way: it is where the program
|
|
190
181
|
* would be, and the word in front took an option no row explains.
|
|
191
182
|
*/
|
|
192
|
-
export declare function readProgramPosition(
|
|
183
|
+
export declare function readProgramPosition(words: readonly LexedWord[]): ProgramPosition;
|
|
193
184
|
/** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
|
|
194
185
|
* stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
|
|
195
186
|
* consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
|
|
@@ -15,31 +15,13 @@ export function writtenAssignmentPrefix(token) {
|
|
|
15
15
|
}
|
|
16
16
|
export { SHELL_CONTROL_PREFIXES };
|
|
17
17
|
export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
|
|
18
|
-
export function isBareControlWord(
|
|
19
|
-
const
|
|
20
|
-
return
|
|
18
|
+
export function isBareControlWord(words, i, set) {
|
|
19
|
+
const w = words[i];
|
|
20
|
+
return w !== undefined && w.raw === w.folded && set.has(w.folded);
|
|
21
21
|
}
|
|
22
|
-
export function isLeadingAssignmentWord(
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
if (word === undefined || raw === undefined)
|
|
26
|
-
return false;
|
|
27
|
-
const written = writtenAssignmentPrefix(word);
|
|
28
|
-
return written !== undefined && prefixWrittenBare(raw, written);
|
|
29
|
-
}
|
|
30
|
-
function prefixWrittenBare(raw, written) {
|
|
31
|
-
if (!raw.startsWith(written.name))
|
|
32
|
-
return false;
|
|
33
|
-
let at = written.name.length;
|
|
34
|
-
if (written.prefix[at] === "[") {
|
|
35
|
-
if (raw[at] !== "[")
|
|
36
|
-
return false;
|
|
37
|
-
const close = raw.indexOf("]", at);
|
|
38
|
-
if (close < 0)
|
|
39
|
-
return false;
|
|
40
|
-
at = close + 1;
|
|
41
|
-
}
|
|
42
|
-
return raw.startsWith(written.appends ? "+=" : "=", at);
|
|
22
|
+
export function isLeadingAssignmentWord(words, i) {
|
|
23
|
+
const w = words[i];
|
|
24
|
+
return w !== undefined && w.assignment !== undefined && w.assignment.bare && w.atCommandPosition;
|
|
43
25
|
}
|
|
44
26
|
export const ARGV_REENTRY_PROGRAM_RE = /^xargs$/;
|
|
45
27
|
export const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
|
|
@@ -63,7 +45,7 @@ export const LAUNCHER_TABLE = new Map([
|
|
|
63
45
|
], { requiredOperands: 1 })],
|
|
64
46
|
["time", {
|
|
65
47
|
optionArity: new Map([
|
|
66
|
-
["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0],
|
|
48
|
+
["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0], ["--", 0],
|
|
67
49
|
["-f", 1], ["-o", 1], ["--format", 1], ["--output", 1],
|
|
68
50
|
]),
|
|
69
51
|
}],
|
|
@@ -108,11 +90,11 @@ function launcherOptionStep(row, tok) {
|
|
|
108
90
|
return undefined;
|
|
109
91
|
}
|
|
110
92
|
export const PROGRAM_POSITION_REFUSAL_KINDS = ["option_at_program_position", "control_syntax_at_program_position"];
|
|
111
|
-
export function readProgramPosition(
|
|
112
|
-
const
|
|
93
|
+
export function readProgramPosition(words) {
|
|
94
|
+
const folded = words.map((w) => w.folded);
|
|
113
95
|
const controlWords = [];
|
|
114
96
|
let i = 0;
|
|
115
|
-
while (i < folded.length && isBareControlWord(
|
|
97
|
+
while (i < folded.length && isBareControlWord(words, i, SHELL_CONTROL_PREFIXES)) {
|
|
116
98
|
const controlWord = folded[i];
|
|
117
99
|
controlWords.push(i);
|
|
118
100
|
i = pastLauncherOptions(controlWord, folded, i + 1);
|
|
@@ -122,7 +104,7 @@ export function readProgramPosition(toks) {
|
|
|
122
104
|
return none(undefined, []);
|
|
123
105
|
const written = i;
|
|
124
106
|
const assignments = [];
|
|
125
|
-
while (i < folded.length && isLeadingAssignmentWord(
|
|
107
|
+
while (i < folded.length && isLeadingAssignmentWord(words, i)) {
|
|
126
108
|
assignments.push(i);
|
|
127
109
|
i++;
|
|
128
110
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LexedSegment, type LexedWord, type ShellLex, type ShellSegmentConnector, type WordPiece } from "../../shell/lex.js";
|
|
2
2
|
/**
|
|
3
3
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
4
4
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -75,16 +75,15 @@ export interface LeadingCommandNameOptions {
|
|
|
75
75
|
* Read a shell operator that sits inside a QUOTED region as ordinary argument TEXT rather than as an
|
|
76
76
|
* operator, so `grep -E "a|b" f.log` parses as the single simple command it is.
|
|
77
77
|
*
|
|
78
|
-
* OPT-IN,
|
|
79
|
-
*
|
|
80
|
-
* {@link quoteMask}'s exactness envelope — the compound classify face
|
|
78
|
+
* OPT-IN, read off the one lexer's result: an operator character bash reads as text (inside quotes, or
|
|
79
|
+
* behind an escape) is text here. Two callers set it — the compound classify face
|
|
81
80
|
* ({@link classifyCompoundReadonlyDetailed}) and the persisted permission-RULE lane (`RULE_LANE_FLOOR`),
|
|
82
|
-
* whose segments come out of
|
|
83
|
-
* DECLARATION face does NOT set it: its contract is "a single simple command with no
|
|
84
|
-
* at all", and it runs on raw tool input with no whole-string gate in front of it.
|
|
81
|
+
* whose segments come out of the same residual predicate ({@link splitShellCompoundSegments}). The
|
|
82
|
+
* `bash_readonly` DECLARATION face does NOT set it: its contract is "a single simple command with no
|
|
83
|
+
* operator characters at all", and it runs on raw tool input with no whole-string gate in front of it.
|
|
85
84
|
*
|
|
86
|
-
* Fail-closed on
|
|
87
|
-
*
|
|
85
|
+
* Fail-closed on a command the lexer cannot delimit: every operator character then counts, exactly as
|
|
86
|
+
* without this option.
|
|
88
87
|
*/
|
|
89
88
|
quotedOperatorsAreText?: boolean;
|
|
90
89
|
/**
|
|
@@ -146,10 +145,36 @@ export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<
|
|
|
146
145
|
* Exported because the tolerating caller owes a PER-SEGMENT judgement in its place, and that judgement
|
|
147
146
|
* must read the same character class this module tolerated — two hand-kept lists would drift, and a
|
|
148
147
|
* drift here is a segment nobody judged. Says nothing about what a caller should DO about a hit: the
|
|
149
|
-
* rule lane's offer face contributes no member for such a segment; the read-only classify face
|
|
150
|
-
*
|
|
148
|
+
* rule lane's offer face contributes no member for such a segment; the read-only classify face passes
|
|
149
|
+
* only the two data-free spellings ({@link isDataFreeRedirection}).
|
|
151
150
|
*/
|
|
152
151
|
export declare function carriesShellRedirection(text: string): boolean;
|
|
152
|
+
/** What a caller asks the residual predicate to pass: see {@link strictResidualRefusal}. */
|
|
153
|
+
export interface StrictResidualOptions {
|
|
154
|
+
/** `<`/`>` are not refused and a redirection is the segment's own business (the rule lane's offer reading). */
|
|
155
|
+
readonly tolerateRedirection?: boolean;
|
|
156
|
+
/** Only the two data-free spellings pass ({@link isDataFreeRedirection}); every other redirection refuses. */
|
|
157
|
+
readonly dataFreeRedirections?: boolean;
|
|
158
|
+
/** The declared home: a word that spells it ({@link homeExpansionOf}) is not an expansion to refuse — unless the
|
|
159
|
+
* command may rebind `HOME` ({@link homeMayBeRebound}), which makes the declared home a guess. */
|
|
160
|
+
readonly homeDir?: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* THE classify faces' whole-string gate, as a PREDICATE over the one lexer's result: the residual space those
|
|
164
|
+
* faces live in is what this function does not refuse. Four passes, in the order the gate's sentences were
|
|
165
|
+
* always minted, so a refused command is refused for the same stated reason:
|
|
166
|
+
* 1. the quote-blind class ({@link SHELL_SEGMENT_QUOTE_BLIND_REJECT}) over every written spelling — a word, a
|
|
167
|
+
* redirection target under `tolerateRedirection` — plus every group, here-document, line-break connector
|
|
168
|
+
* and (unless tolerated or data-free) redirection token;
|
|
169
|
+
* 2. the expansion character where bash expands it ({@link expandsDollar}), the home-directory word excepted
|
|
170
|
+
* under `homeDeclared`;
|
|
171
|
+
* 3. a backgrounding `&` and a `|&`;
|
|
172
|
+
* 4. a word whose value the text does not settle ({@link UNRESOLVED_SENTENCE}) — a compound array, an
|
|
173
|
+
* off-table ANSI-C escape, a subscript or parameter expansion this lexer does not read.
|
|
174
|
+
* A command the lexer cannot delimit is refused for the class if its text carries one (the reading the gate
|
|
175
|
+
* gave an unbalanced string), else for the parse failure. Returns the refusal REASON, or `undefined` to proceed.
|
|
176
|
+
*/
|
|
177
|
+
export declare function strictResidualRefusal(lexed: ShellLex, source: string, opts?: StrictResidualOptions): string | undefined;
|
|
153
178
|
/** What {@link splitShellCompoundSegments} produces: the connector-delimited segments, plus which of
|
|
154
179
|
* them a PIPE fed (the read-only classifier's stdin arm needs that distinction; a rule lane does not). */
|
|
155
180
|
export interface ShellCompoundSegments {
|
|
@@ -163,39 +188,28 @@ export interface ShellCompoundSegments {
|
|
|
163
188
|
* one this walk cannot verify. A consumer that does not read it gets the unverified reading, which is
|
|
164
189
|
* the fail-closed one. */
|
|
165
190
|
connectors: ShellSegmentConnector[];
|
|
191
|
+
/** Parallel to {@link segments}: the segments as the lexer minted them — the words (with their facts) and
|
|
192
|
+
* the redirections of each. A consumer that tokenizes reads these; the text is for the sentences. */
|
|
193
|
+
lexed: readonly LexedSegment[];
|
|
166
194
|
}
|
|
167
195
|
/**
|
|
168
196
|
* Split a compound command into the segments bash would run, or refuse the whole string.
|
|
169
197
|
*
|
|
170
|
-
* THE one segmentation in this repository
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* is untouched (`<`/`>` neither quote nor bound) and the refusal moves to the caller's per-segment
|
|
186
|
-
* vetting via {@link carriesShellRedirection}. The default refuses the whole string as it always did.
|
|
187
|
-
* 2. one trailing `;` is stripped — a no-op terminator, not an empty command. OPTIONAL, because it is
|
|
188
|
-
* the one step whose answer depends on what the caller is asking. A face that asks "which programs
|
|
189
|
-
* does this run" wants it stripped (`ls;` runs `ls`, nothing else). A face that asks "is this ONE
|
|
190
|
-
* command" must NOT strip it: `"keep"` makes `ls;` two segments, the second empty, so the caller's
|
|
191
|
-
* own empty-command rule puts the whole string outside its lane — which is what keeps a statement
|
|
192
|
-
* like "this rule form never matches a command containing a connector" literally true instead of
|
|
193
|
-
* true-except-for-one-spelling.
|
|
194
|
-
* 3. the quote-aware connector scan over `;` `&&` `||` `|` — the strict mode of the ONE lexer
|
|
195
|
-
* ({@link lexShellText}). A lone `&` is REFUSED rather than split on: it backgrounds a process that
|
|
196
|
-
* outlives the command, which is not a combinator any face here is willing to reason about.
|
|
197
|
-
* Unbalanced quoting falls back to the quote-blind scan — strictly MORE segments, each of which the
|
|
198
|
-
* caller must still vet, i.e. the fail-closed direction.
|
|
198
|
+
* THE one segmentation in this repository, and the one place the classify faces' residual predicate is
|
|
199
|
+
* applied: the persisted permission-rule lane and the read-only classify face both read their segments
|
|
200
|
+
* here, so a drift in what a deny rule is even looking at cannot open between them.
|
|
201
|
+
*
|
|
202
|
+
* Two steps: the trailing-terminator strip, then ONE lex ({@link lexShell}) judged by
|
|
203
|
+
* {@link strictResidualRefusal}. The strip is OPTIONAL because it is the one step whose answer depends on
|
|
204
|
+
* what the caller is asking. A face that asks "which programs does this run" wants it stripped (`ls;` runs
|
|
205
|
+
* `ls`, nothing else). A face that asks "is this ONE command" must NOT strip it: `"keep"` makes `ls;` two
|
|
206
|
+
* segments, the second empty, so the caller's own empty-command rule puts the whole string outside its lane
|
|
207
|
+
* — which is what keeps a statement like "this rule form never matches a command containing a connector"
|
|
208
|
+
* literally true instead of true-except-for-one-spelling. `redirection` picks which redirections the
|
|
209
|
+
* predicate passes: none (the default, the incumbent whole-string refusal), every one (`"tolerate"` — the
|
|
210
|
+
* refusal moves to the caller's per-segment vetting via {@link carriesShellRedirection}), or the two
|
|
211
|
+
* data-free spellings (`"data-free"`, the read-only classify face). `homeDir` lets the word spelling the
|
|
212
|
+
* declared home through the expansion refusal.
|
|
199
213
|
*
|
|
200
214
|
* Empty segments are NOT refused here (`;;`, a leading `;`, a trailing `&&`) — they come back as empty
|
|
201
215
|
* strings, and every caller's per-segment vetting rejects them through its own empty-command rule. The
|
|
@@ -203,7 +217,8 @@ export interface ShellCompoundSegments {
|
|
|
203
217
|
*/
|
|
204
218
|
export declare function splitShellCompoundSegments(source: string, options?: {
|
|
205
219
|
trailingTerminator?: "strip" | "keep";
|
|
206
|
-
redirection?: "reject" | "tolerate";
|
|
220
|
+
redirection?: "reject" | "tolerate" | "data-free";
|
|
221
|
+
homeDir?: string;
|
|
207
222
|
}): ShellCompoundSegments | {
|
|
208
223
|
reject: string;
|
|
209
224
|
};
|
|
@@ -213,7 +228,7 @@ export declare function splitShellCompoundSegments(source: string, options?: {
|
|
|
213
228
|
* {@link coarseReadonlyCheck} (the single {@link parseLeadingCommandName} parser + bare-name allowlist).
|
|
214
229
|
* All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
|
|
215
230
|
*
|
|
216
|
-
* G2 — the split CARRIES QUOTE STATE (
|
|
231
|
+
* G2 — the split CARRIES QUOTE STATE (the one lexer): a connector character inside a quoted region is
|
|
217
232
|
* argument text, not a boundary, so `grep -E "error:|BUILD FAILED" x.log | tail -10` is two segments
|
|
218
233
|
* (`grep …` and `tail -10`) rather than four, and the phantom head `BUILD` no longer decides the verdict.
|
|
219
234
|
* The whole compound face is what this fixes: a report of an every-command approval prompt on a read-only
|
|
@@ -221,8 +236,8 @@ export declare function splitShellCompoundSegments(source: string, options?: {
|
|
|
221
236
|
* ordinary spelling of that idiom.
|
|
222
237
|
*
|
|
223
238
|
* Soundness — why the quote-aware split is NOT a relaxation. Backslash/`$`/backtick/subshells/redirection/
|
|
224
|
-
* line breaks are
|
|
225
|
-
*
|
|
239
|
+
* line breaks are refused first (the two data-free forms of {@link isDataFreeRedirection} pass, and they
|
|
240
|
+
* move no data into any file), so in the residual space `'`/`"` pairing is
|
|
226
241
|
* the ENTIRETY of quoting and the mask's regions are exactly bash's quoted regions. The unquoted connectors it splits on are therefore exactly bash's command boundaries:
|
|
227
242
|
* every program bash runs still starts at the head of some segment this classifier vetted, and its argv[0]
|
|
228
243
|
* is the head token (a token carrying a quote/brace/glob/tilde character is rejected by the parser's
|
|
@@ -231,14 +246,13 @@ export declare function splitShellCompoundSegments(source: string, options?: {
|
|
|
231
246
|
* program was ever going to run — are gone. Those could only ever ADD a rejection, so removing them
|
|
232
247
|
* removes false demotions and admits no additional program.
|
|
233
248
|
*
|
|
234
|
-
* UNBALANCED QUOTING
|
|
235
|
-
* is one bash cannot parse into commands at all, so there is no "true" segmentation to be accurate about
|
|
236
|
-
* the blind scan yields strictly MORE segments, each of which must still pass the allowlist.
|
|
249
|
+
* UNBALANCED QUOTING is a parse failure, refused whole (fail-closed). A string with an unterminated quote
|
|
250
|
+
* is one bash cannot parse into commands at all, so there is no "true" segmentation to be accurate about.
|
|
237
251
|
*
|
|
238
252
|
* A LONE `&` is hard-rejected (not a connector): it backgrounds a process that survives the shell — the node
|
|
239
253
|
* execution env spools stdio and deliberately leaves descendants alive on completion — escaping TaskStop and
|
|
240
254
|
* background-lifecycle governance entirely. Backgrounding is not a read-only combinator, even for a listed
|
|
241
|
-
* reader (`tail -f x &` outlives the call). `|&`
|
|
255
|
+
* reader (`tail -f x &` outlives the call). `|&` is refused by name.
|
|
242
256
|
* One TRAILING `;` is stripped (a no-op terminator); any other empty segment (`;;`, leading `;`, trailing
|
|
243
257
|
* `&&`/`|`) rejects via the parser's empty-command rule.
|
|
244
258
|
*
|
|
@@ -269,7 +283,7 @@ export interface BashReadonlyRootBoundary {
|
|
|
269
283
|
* should pass the LIVE working directory, or a `..` operand will be judged against the wrong base. */
|
|
270
284
|
cwd?: string;
|
|
271
285
|
/** Expansion for a leading `~` — and the value the read faces substitute for the `$HOME` / `${HOME}` word
|
|
272
|
-
* spellings
|
|
286
|
+
* spellings ({@link homeExpansionOf}). Without it a `~`-rooted operand is unresolvable
|
|
273
287
|
* and demotes (with no out-of-root signal — the classifier does not know where it points, which is a
|
|
274
288
|
* different verdict from knowing it points outside). */
|
|
275
289
|
homeDir?: string;
|
|
@@ -400,39 +414,29 @@ export declare function resolveOperandSpellings(base: string | undefined, operan
|
|
|
400
414
|
* for the reason it is not a launcher: it hands a BUILT command line to a shell. */
|
|
401
415
|
export declare const SHELL_REENTRY_PROGRAM_RE: RegExp;
|
|
402
416
|
/**
|
|
403
|
-
* Does this
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
* declared unresolvable — the same verdict `~user` already gets, and the same direction the whole file
|
|
414
|
-
* errs in. Quoting is honoured exactly as bash does it: nothing expands inside single quotes, while `$`
|
|
415
|
-
* and a backtick still expand inside double quotes (braces do not).
|
|
416
|
-
*
|
|
417
|
-
* Deliberately NOT in the set: the GLOB characters `* ? [ ]`. A glob expands only to names that already
|
|
418
|
-
* exist in the directory the pattern itself names, and a path component can never match a `/` — so a glob
|
|
419
|
-
* cannot manufacture a traversal that is not already textually present (`cat ../*` still carries `../`
|
|
420
|
-
* and is judged on it), while listing them would demote the everyday `ls *.txt`. `$`, backtick and the
|
|
421
|
-
* parentheses are redundant against a compliant caller (both faces reject them upstream, whole-string;
|
|
422
|
-
* a home-directory variable word never arrives as `$` — {@link foldHomeVariableSpelling} substituted the
|
|
423
|
-
* declared value before segmentation) and are kept here only so this scan is self-standing.
|
|
417
|
+
* Does the shell REWRITE this word before the program sees it — a brace list, a parameter, command, process or
|
|
418
|
+
* arithmetic expansion, or a `$` / backtick / brace character where bash reads it (unquoted; `$` and a backtick
|
|
419
|
+
* also inside double quotes)? Read off the lexer's pieces. Brace expansion runs BEFORE tilde expansion and
|
|
420
|
+
* before the word ever reaches the program, so `cat {<abs>,}` hands the program an absolute path no spelling
|
|
421
|
+
* shows, and `ls {~,}` a home directory the `~` arm of {@link resolveOperandLexically} never got to refuse —
|
|
422
|
+
* both were real out-of-root reads. Rather than emulate expansion (the classifier resolves paths lexically and
|
|
423
|
+
* has no filesystem), such a word is declared unresolvable — the direction the whole file errs in.
|
|
424
|
+
*
|
|
425
|
+
* Deliberately NOT here: the GLOB characters ({@link globInWord}) — a glob only ever names entries of a
|
|
426
|
+
* directory that is already on the command line — and a tilde, which the resolver reads.
|
|
424
427
|
*/
|
|
425
|
-
export declare function
|
|
428
|
+
export declare function expansionInWord(w: {
|
|
429
|
+
readonly pieces: readonly WordPiece[];
|
|
430
|
+
}): boolean;
|
|
426
431
|
/**
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
* on the command line, so a caller holding a filesystem can decide it — see
|
|
432
|
-
* {@link CompoundReadonlyVerdict.undecidedPaths}. Both quoting forms suppress globbing in bash, so unlike
|
|
433
|
-
* the expansion walk there is no `"`-open arm here.
|
|
432
|
+
* Does this word carry PATHNAME expansion (`*`, `?`, `[`), unquoted? Split from {@link expansionInWord} because
|
|
433
|
+
* the two have different consequences, not different detection: a brace/variable/command expansion can produce
|
|
434
|
+
* ANY word, so it is unresolvable; a glob only ever names entries of a directory that is already on the command
|
|
435
|
+
* line, so a caller holding a filesystem can decide it. Both quoting forms suppress globbing in bash.
|
|
434
436
|
*/
|
|
435
|
-
export declare function
|
|
437
|
+
export declare function globInWord(w: {
|
|
438
|
+
readonly pieces: readonly WordPiece[];
|
|
439
|
+
}): boolean;
|
|
436
440
|
/**
|
|
437
441
|
* Does this token supply the verb's FIRST-OPERAND slot, so that the first operand is a FILE rather than the
|
|
438
442
|
* slot ({@link VerbOptionSlots.firstOperand})? One reader, every verb with such a row.
|
|
@@ -811,15 +815,6 @@ export declare function inlineProgramClause(model: InlineProgramModel, args: rea
|
|
|
811
815
|
* command name. Names-only traversals (`find`, `ls -R`, `du`, `tree`, an archive write) are in the table
|
|
812
816
|
* and answer `false` here — see {@link RecursiveReadFormModel.discloses}. */
|
|
813
817
|
export declare function segmentSelectsRecursiveRead(name: string, args: readonly string[]): boolean;
|
|
814
|
-
/** `raw` is the word as WRITTEN; it defaults to the folded spelling, which is the reading that expands most. */
|
|
815
|
-
export declare function assignmentWordPayload(word: string, raw?: string): {
|
|
816
|
-
kind: "value";
|
|
817
|
-
value: string;
|
|
818
|
-
} | {
|
|
819
|
-
kind: "unresolved";
|
|
820
|
-
reason: string;
|
|
821
|
-
value?: string;
|
|
822
|
-
} | undefined;
|
|
823
818
|
/** WHICH programs are DECLARED not to open the value of a `NAME=value` ARGUMENT — an allow-list, because
|
|
824
819
|
* the other polarity was measured false. `make PREFIX=/usr/local`, `cmake -D…`, `docker run -e HOME=/root`
|
|
825
820
|
* name a build prefix and an environment value their program never opens, and judging those was the one
|
|
@@ -839,31 +834,48 @@ export declare const ASSIGNMENT_VALUE_NOT_PATHS: ReadonlySet<string>;
|
|
|
839
834
|
* disagree. The LEADING assignments (both spellings) are the program-position reader's own step, so this
|
|
840
835
|
* seat no longer strips them a second time: a second strip read the quoted `'A+=x'` as an assignment the
|
|
841
836
|
* one reader calls a program name, which is two readers with two answers. */
|
|
842
|
-
export declare function argvProgramOf(
|
|
837
|
+
export declare function argvProgramOf(words: readonly LexedWord[]): {
|
|
843
838
|
program: string;
|
|
844
839
|
from: number;
|
|
845
840
|
} | undefined;
|
|
846
841
|
export declare function attachedOptionPayloads(tok: string): string[];
|
|
847
842
|
/**
|
|
848
|
-
* The home directory's VARIABLE
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* home (
|
|
856
|
-
*
|
|
857
|
-
* reading of the rewritten word: the UNQUOTED spelling only for a value with no `$IFS` whitespace, glob or
|
|
858
|
-
* shell metacharacter (word splitting / pathname expansion / a connector this file would then split on — none
|
|
859
|
-
* of which a real expansion is subject to here), the double-quoted one for any value carrying no `"`, `$`,
|
|
860
|
-
* backtick, backslash or line break (the characters that would change the quote structure or meet the
|
|
861
|
-
* hard-reject). Everything else is left as written: a single-quoted spelling does not expand, and a mid-word
|
|
862
|
-
* or suffixed one (`x$HOME`, `$HOME.bak`, `$HOMES`) still meets the substitution hard-reject. Bails whole on
|
|
863
|
-
* an escape or a backtick in the command (the hard-reject refuses those strings regardless, and the quote
|
|
864
|
-
* walk below is exact only without them) and on unbalanced quoting.
|
|
843
|
+
* The home directory's VARIABLE spelling as a word FACT: `$HOME` / `${HOME}` as the word's FIRST piece,
|
|
844
|
+
* unquoted or double-quoted, followed by `/`, by a double-quoted piece, or by nothing (unquoted), or — inside
|
|
845
|
+
* the quotes — by `/` or the closing quote; every later piece literal. Those are exactly the words whose expansion
|
|
846
|
+
* is the DECLARED home and nothing else, so `spelled` is what bash hands the program: `<home><rest>`. Everything
|
|
847
|
+
* else is left to be the expansion it is: a single-quoted spelling does not expand, a mid-word or suffixed one
|
|
848
|
+
* (`x$HOME`, `$HOME.bak`, `$HOMES`) is a substitution the classify faces refuse and the read station cannot
|
|
849
|
+
* place, and an unquoted spelling of a home bash would split or glob ({@link HOME_EXPANDS_TO_ONE_WORD}) is not
|
|
850
|
+
* one word. The caller reads it only against a declared home (with none declared the child's `$HOME` may differ
|
|
851
|
+
* from what `~` falls back to) and only when the command cannot rebind `HOME` ({@link homeMayBeRebound}).
|
|
865
852
|
*/
|
|
866
|
-
export declare function
|
|
853
|
+
export declare function homeExpansionOf(w: LexedWord, home: string): {
|
|
854
|
+
readonly spelled: string;
|
|
855
|
+
} | undefined;
|
|
856
|
+
/** May this command text REBIND `HOME` before a later word expands it? Read LEXICALLY, not by grammar: the text
|
|
857
|
+
* spells the name `HOME` somewhere other than as a plain read ({@link HOME_PLAIN_READ}) — after `\<newline>`
|
|
858
|
+
* continuations are joined and quotes (with the `$` of a `$'…'` / `$"…"` opener) and backslashes are removed, so
|
|
859
|
+
* `"HO"ME`, `HO\ME`, `HO$'ME'` and an `eval` string spell it the same as a bare word. That covers an assignment at ANY nesting depth (a `{ … }` group, a
|
|
860
|
+
* function body, a `case` arm, a loop), a declaration builtin's operand, a `for` / `select` variable, a `read` /
|
|
861
|
+
* `printf -v` / `mapfile` / `getopts` / `unset` target, a nameref value, an arithmetic or `${HOME:=…}` assignment,
|
|
862
|
+
* without naming any of them. A reader that substitutes the declared home for `$HOME` / `${HOME}` / a leading `~`
|
|
863
|
+
* asks this first; a text it answers yes for leaves those words unplaced (the fail-closed side).
|
|
864
|
+
* WHY not a grammar reading: every "where does the name stand" reading of this question (the leading assignment
|
|
865
|
+
* words, a declaration builtin's operands) has had a nesting level or a builtin it did not see.
|
|
866
|
+
* ACCEPTED COST: a text that merely spells the name while reading through the home — `echo HOME; cat ~/x`, a longer
|
|
867
|
+
* name ending in it (`MY_HOME=…`), a comment — asks instead of running.
|
|
868
|
+
* A name the shell COMPUTES spells no `HOME` to find, so four more readings count as a rebinding: an unquoted GLOB
|
|
869
|
+
* word whose pattern can match the name `HOME` or an array element `HOME[…]` of it ({@link globMayMatchHome} —
|
|
870
|
+
* `read H?ME`, `unset [H]OME`, `read H?ME[0]`, `unset *` glob against the working directory when an entry named
|
|
871
|
+
* `HOME` sits there); an unquoted EXTGLOB opener ({@link hasUnquotedExtglobOpener} — `read !(x)` matches nearly
|
|
872
|
+
* every name); an unquoted BRACE list ({@link wordHasBraceList} — `read H{O,X}ME` expands to a list whose first
|
|
873
|
+
* word is the name); an option word that carries a glob ({@link optionWordCarriesGlob} — `read -aH?ME`, where a
|
|
874
|
+
* matched entry named `-aHOME` hands the builtin `-a HOME`); and a `printf -v` name that is not a literal
|
|
875
|
+
* identifier ({@link printfNamesComputed} — `printf` opens no path, so its words are no candidate of their own).
|
|
876
|
+
* Elsewhere a computed word is its own unresolvable candidate, and an escape the lexer does not decode is
|
|
877
|
+
* unresolved on its own (asked). */
|
|
878
|
+
export declare function homeMayBeRebound(text: string): boolean;
|
|
867
879
|
/**
|
|
868
880
|
* {@link classifyCompoundReadonly} with the structured verdict. The name/operator/blocking arms
|
|
869
881
|
* run FIRST and unchanged, so an existing demotion keeps its exact reason and never acquires the
|
|
@@ -949,16 +961,12 @@ export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
|
|
|
949
961
|
* verdict carrying {@link CompoundReadonlyVerdict.undecidedPaths} (an unexpanded glob) rejects,
|
|
950
962
|
* because the probe face is reason-only and a repeated read of a runtime-decided target must not
|
|
951
963
|
* auto-allow.
|
|
952
|
-
* G5 STRUCTURE SOUNDNESS (the parser sees what bash executes).
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
* parses — a `done` hidden in quotes either unbalances (reject) or leaves a body token that fails
|
|
959
|
-
* the allowlist. Within the body, the quote-blind `;` split inherits the compound face's
|
|
960
|
-
* soundness argument: with all expansion metacharacters banned, quotes only ever HIDE connectors,
|
|
961
|
-
* so mis-splits are over-fine and the error direction is reject, never widen.
|
|
964
|
+
* G5 STRUCTURE SOUNDNESS (the parser sees what bash executes). Any non-ASCII / control whitespace
|
|
965
|
+
* ({@link NON_ASCII_WHITESPACE}) rejects the whole command up front, the reference implementation's
|
|
966
|
+
* form. The skeleton is then read off the ONE lexer's segments and words ({@link pollLoopShapeOf}) — the
|
|
967
|
+
* same reading bash gives the text: an unterminated quote is a parse failure (reject), a `done` hidden in
|
|
968
|
+
* quotes is a word that is not `done`, and the var/iterable words must be written bare. The body's
|
|
969
|
+
* segments are the lexer's, so a connector inside quotes is text, never a boundary.
|
|
962
970
|
* G6 ENVIRONMENT INTEGRITY. The loop variable must be a SINGLE LOWERCASE LETTER: the `for`
|
|
963
971
|
* assignment mutates a shell variable, and a name like `PATH`/`IFS`/`LD_PRELOAD`/`BASH_ENV`
|
|
964
972
|
* would change how the body's commands RESOLVE (`for PATH in dir; do cat x; done` would run
|