@sema-agent/core 7.45.0 → 7.46.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 +246 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +5 -3
- package/dist/core/allow-entry.js +3 -1
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +5 -0
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +9 -3
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -40
- package/dist/core/permission-rules.js +50 -43
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +11 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +30 -8
- package/dist/core/tool-policy.js +9 -7
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +22 -22
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -3
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -129,6 +129,9 @@ export interface FsViolation {
|
|
|
129
129
|
* lands it outside every POSIX/drive root, i.e. a location the working directory does not contain.
|
|
130
130
|
*/
|
|
131
131
|
export declare function isAbsolutePathForm(p: string): boolean;
|
|
132
|
+
/** Does this absolute spelling name the opening process's STANDARD INPUT (`/dev/stdin`, `/dev/fd/0`, `/proc/<pid>/fd/0`),
|
|
133
|
+
* folded lexically first (`/dev/./stdin`, `/dev//fd/0`)? */
|
|
134
|
+
export declare function namesStandardInput(path: string): boolean;
|
|
132
135
|
/**
|
|
133
136
|
* Windows-FAMILY spelling of a path (extracted here so {@link canonicalizeTarget} judges
|
|
134
137
|
* the UNC family by the same rule): drive-rooted (`C:\…`/`C:/…`), UNC-backslash (`\\host\share`), or a
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readDenySentence } from "./read-deny.js";
|
|
|
3
3
|
import { homeViewsOf } from "./read-deny-home.js";
|
|
4
4
|
import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
|
|
5
5
|
import { pathTargetValue, protectivePathTargetOf } from "../../core/tool-registry.js";
|
|
6
|
+
import { readPathViews } from "../../core/effective-path-target.js";
|
|
6
7
|
import { withGateTimeout, DEFAULT_GATE_AWAIT_TIMEOUT_MS } from "../../core/gate-bounded-await.js";
|
|
7
8
|
import { thrownText } from "../../core/thrown-value.js";
|
|
8
9
|
export function fileArgPath(args) {
|
|
@@ -41,6 +42,10 @@ const BLOCKED_DEVICE_PATHS = new Set([
|
|
|
41
42
|
function isProcStdioFd(key) {
|
|
42
43
|
return key.startsWith("/proc/") && (key.endsWith("/fd/0") || key.endsWith("/fd/1") || key.endsWith("/fd/2"));
|
|
43
44
|
}
|
|
45
|
+
export function namesStandardInput(path) {
|
|
46
|
+
const k = path.startsWith("/") ? normalizeAbsPathLexically(path) : "";
|
|
47
|
+
return k === "/dev/stdin" || k === "/dev/fd/0" || (k.startsWith("/proc/") && k.endsWith("/fd/0"));
|
|
48
|
+
}
|
|
44
49
|
const PROC_SENSITIVE_SUFFIXES = ["/environ", "/cmdline", "/auxv", "/maps", "/mem", "/stat"];
|
|
45
50
|
function isProcSensitiveFile(key) {
|
|
46
51
|
return key.startsWith("/proc/") && PROC_SENSITIVE_SUFFIXES.some((suf) => key.endsWith(suf));
|
|
@@ -457,7 +462,8 @@ function within(rootCanonical, p) {
|
|
|
457
462
|
return pN.identity.startsWith(rootN.identity.endsWith(SEP) ? rootN.identity : rootN.identity + SEP);
|
|
458
463
|
}
|
|
459
464
|
export async function resolveKey(env, rootCanonical, path, signal, baseCwd, additionalRoots, exactFileReadExemption, readDeny, containmentFace) {
|
|
460
|
-
const
|
|
465
|
+
const views = await readPathViews(env, path, baseCwd ?? rootCanonical, { root: rootCanonical, ...(baseCwd !== undefined ? { cwd: baseCwd } : {}) }, signal, { hops: readDeny !== undefined });
|
|
466
|
+
const canon = views.canonical ?? (await canonicalizeTarget(env, path, signal, pathFamilyOf({ root: rootCanonical }), baseCwd));
|
|
461
467
|
if (!canon.ok)
|
|
462
468
|
return { ok: false, violation: { code: "invalid", message: canon.message } };
|
|
463
469
|
const key = canon.key;
|
|
@@ -466,8 +472,9 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
|
|
|
466
472
|
return { ok: false, violation: { code: "invalid", message: `path "${path}" is a blocked device/special file (reading it can hang the process); refused.` } };
|
|
467
473
|
}
|
|
468
474
|
if (readDeny !== undefined) {
|
|
469
|
-
const judged = { aliasResolved: canon.aliasResolved === true };
|
|
470
|
-
|
|
475
|
+
const judged = { aliasResolved: canon.aliasResolved === true, hops: views.hops };
|
|
476
|
+
const lexical = views.lexical?.kind === "path" ? spellingOfIdentity(views.lexical.identity, pathFamilyOf({ root: rootCanonical })) : undefined;
|
|
477
|
+
let hit = readDeny.matchTarget(key, lexical, judged);
|
|
471
478
|
if (hit === null) {
|
|
472
479
|
const homeView = await homeViewsOf(env, readDeny, signal).of(key);
|
|
473
480
|
if (homeView !== undefined)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The candidates ONE program's words hand the read station — the program read model's rows read word by word: a
|
|
3
|
+
* listed reader's operands and option-value slots, an unlisted program's words (its own row read where it has one,
|
|
4
|
+
* read the speculative way where it has none), a subcommand's words by the subcommand's row, a module's program text,
|
|
5
|
+
* an assignment word's value. `shell-read-plan.ts` threads the segments and their bases; this module is what one
|
|
6
|
+
* segment's argv offers.
|
|
7
|
+
*/
|
|
8
|
+
import { type LexedWord } from "../../shell/lex.js";
|
|
9
|
+
import { type Read } from "./shell-read-word.js";
|
|
10
|
+
import { type BindingTable, type BoundValue } from "./shell-read-bindings.js";
|
|
11
|
+
import { subcommandOf, type ArgRole, type ProgramRow } from "./program-read-model.js";
|
|
12
|
+
import type { ReadCandidate, ReadCandidateProvenance } from "./shell-read-plan.js";
|
|
13
|
+
/** A candidate before the threading fills its bases. `alternatives`: the other spellings its word may expand to (a name
|
|
14
|
+
* bound to several values) — one candidate each once the segment's reads are read. */
|
|
15
|
+
export type Bare = Omit<ReadCandidate, "bases"> & {
|
|
16
|
+
readonly alternatives?: readonly string[];
|
|
17
|
+
readonly floors?: readonly BoundValue[];
|
|
18
|
+
};
|
|
19
|
+
/** What reading a segment's words found beside its candidates. */
|
|
20
|
+
export interface WordContext {
|
|
21
|
+
/** The names at this segment. */
|
|
22
|
+
readonly table: BindingTable;
|
|
23
|
+
/** Shell text the arithmetic evaluator runs (a substitution in a subscript it evaluates): the segment re-enters it. */
|
|
24
|
+
readonly reentered: string[];
|
|
25
|
+
/** Program text the station does not read — inline text in a language that may open files, or a program read from
|
|
26
|
+
* standard input: why the segment's reads are not established. */
|
|
27
|
+
programText?: string;
|
|
28
|
+
/** Some program text was found in a slot (whether or not it may open files). */
|
|
29
|
+
programTextSeen: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** The subcommand whose row says how ITS words name paths (operands or a traversal of its own), or `undefined`: a
|
|
32
|
+
* subcommand whose row states only its grammar — its message slots, read by {@link messageWords} — leaves its words to
|
|
33
|
+
* the program's own reading. */
|
|
34
|
+
export declare function pathSubcommandOf(row: ProgramRow | undefined, args: readonly string[]): ReturnType<typeof subcommandOf>;
|
|
35
|
+
/** The sentence of inline program text the station does not read. */
|
|
36
|
+
export declare const programTextSentence: (language: string) => string;
|
|
37
|
+
/** One candidate without its bases (filled by the threading pass): the word's spelling — or `spelled`, a caller's
|
|
38
|
+
* reading of PART of it (an option payload, a `key=value` value) — with the word's own expansion facts. A word whose
|
|
39
|
+
* names may hold several values offers each as its whole spelling; a PART of such a word is not followed through them
|
|
40
|
+
* (it is unresolvable). */
|
|
41
|
+
export declare function bare(r: Read, provenance: ReadCandidateProvenance, spelled?: string): Bare;
|
|
42
|
+
/** The reads one value of a declared role hands over: a candidate per file it names, or — a value the program expands
|
|
43
|
+
* past the listed bound — the word itself, unresolvable. A mount source is a read the daemon makes of the whole tree
|
|
44
|
+
* under it (a read and a traversal root); an object path
|
|
45
|
+
* `<rev>:<path>` offers the word as a path and the `<path>` it spells to the deny judge; program text is never a path
|
|
46
|
+
* ({@link programTextReads}). `alsoOperand`: the walk read the option past the first operand, where the program may
|
|
47
|
+
* have stopped reading options — a path role is still offered (more candidates), but no program text is read there:
|
|
48
|
+
* the word is the operands' (the script's own argument), as the program's delivery reading says. */
|
|
49
|
+
export declare function roleReads(r: Read, provenance: ReadCandidateProvenance, value: string, role: ArgRole, globs: "expanded" | "literal", ctx: WordContext, alsoOperand?: boolean): Bare[];
|
|
50
|
+
/**
|
|
51
|
+
* The candidates a `NAME=value` word hands the judge, both facts about its VALUE (the word is a path to nobody):
|
|
52
|
+
* what path it NAMES, and what the text cannot SETTLE — placed as an operand is wherever an environment value
|
|
53
|
+
* names ONE path (a leading tilde against the declared home; a `:` joins a second). Where the text runs nothing able
|
|
54
|
+
* to open a file, {@link planShellReads} drops them: the value names a path to nobody.
|
|
55
|
+
*
|
|
56
|
+
* Bash applies its assignment-word expansion to such a word even when it is an ARGUMENT to a simple command, so
|
|
57
|
+
* the program receives `NAME=<expanded value>`, and a program that reads its value as a path (`dd if=…`, a build
|
|
58
|
+
* or tool variable, a launcher's environment word) opens what the value names. Two facts of the lexer's
|
|
59
|
+
* {@link AssignmentFact} are read: a tilde the shell EXPANDS in the value (at its start and after each `:`, where
|
|
60
|
+
* unquoted — `value.expansions`) leaves the path unsettled, and the append form (`NAME+=value`) names `<whatever
|
|
61
|
+
* NAME held>` + value, so the whole value is not in the text — but the text it appends IS, and an unset or empty
|
|
62
|
+
* NAME makes it the whole value, so it is a candidate too. A value that is a substitution is the nested plan
|
|
63
|
+
* its body already is, never a candidate of its own text (its placeholder is not a path).
|
|
64
|
+
*/
|
|
65
|
+
export declare function pushAssignment(reads: Bare[], w: LexedWord, provenance: ReadCandidateProvenance): void;
|
|
66
|
+
/** Every word of a segment whose program's grammar this parser does not model, from `from` onward: option
|
|
67
|
+
* payloads and path-shaped option words included, the launcher chain's own words excluded, a program declared
|
|
68
|
+
* to open no path operand handing only the environment words in front of it. A program with NO row is read the
|
|
69
|
+
* speculative way: every word is still handed over, but the filesystem decides it
|
|
70
|
+
* ({@link ReadCandidate.speculative}). */
|
|
71
|
+
export declare function unlistedWords(reads: Bare[], toks: readonly Read[], from: number, launcherIndices: ReadonlySet<number>, provenance: ReadCandidateProvenance, ctx: WordContext, program?: {
|
|
72
|
+
readonly index: number;
|
|
73
|
+
readonly basename: string;
|
|
74
|
+
}): void;
|
|
75
|
+
/** The positional operands and declared option-value slots of a listed reader — the program read model's argv walk,
|
|
76
|
+
* one candidate per word the program may open, plus the traversal roots of a recursive form. A subcommand's words
|
|
77
|
+
* are read by its own row; the words a module takes as its program text are program text. */
|
|
78
|
+
export declare function listedVerbCandidates(name: string, argReads: readonly Read[], reads: Bare[], ctx: WordContext): void;
|
|
79
|
+
/** The subscript of a variable-name word (`a[…]`), as the arithmetic text that is evaluated; the empty text for a plain
|
|
80
|
+
* name. */
|
|
81
|
+
export declare function subscriptOnly(word: string): string;
|
|
82
|
+
/** An assignment word's arithmetic: its own subscript (`a[$(…)]=1`, quoted or not — not for a builtin that takes the
|
|
83
|
+
* name `asWritten`), and its value where the name carries the integer attribute (`declare -i n; n='a[$(…)]'`, bash's
|
|
84
|
+
* own `OPTIND` / `RANDOM` / …). */
|
|
85
|
+
export declare function assignmentArithmetic(w: LexedWord, integer: boolean, ctx: WordContext, asWritten?: boolean): void;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { literalTildeSpelling } from "../../shell/lex.js";
|
|
2
|
+
import { expandsOf, readOfText, rewritten } from "./shell-read-word.js";
|
|
3
|
+
import { arithmeticSubstitutions } from "./shell-read-bindings.js";
|
|
4
|
+
import { argvProgramOf, attachedOptionPayloads, isPathShapedToken } from "./bash-readonly-classifier.js";
|
|
5
|
+
import { assignmentArgumentsAreNotPaths, evaluatesOperands, messageWords, moduleTailOf, objectPathsOf, opensNoOperand, programRow, programTextMayOpen, programTextOpensOf, readArgs, selectsContentsTraversal, slotValueCandidates, subcommandOf, valuesAreLiteral, walksCwdWithoutOperand, wordsReadBySomeImplementations, } from "./program-read-model.js";
|
|
6
|
+
export function pathSubcommandOf(row, args) {
|
|
7
|
+
const sub = subcommandOf(row, args);
|
|
8
|
+
return sub !== undefined && (sub.row.operands !== undefined || sub.row.traversal !== undefined) ? sub : undefined;
|
|
9
|
+
}
|
|
10
|
+
export const programTextSentence = (language) => `runs inline ${language} program text, which may open files this station does not read`;
|
|
11
|
+
export function bare(r, provenance, spelled = r.folded) {
|
|
12
|
+
const whole = spelled === r.folded;
|
|
13
|
+
if (!whole && r.values !== undefined)
|
|
14
|
+
return { spelled, raw: r.raw, provenance, expands: "unresolvable" };
|
|
15
|
+
return {
|
|
16
|
+
spelled,
|
|
17
|
+
raw: r.raw,
|
|
18
|
+
provenance,
|
|
19
|
+
expands: r.expands,
|
|
20
|
+
...(whole && r.values !== undefined ? { alternatives: r.values.slice(1) } : {}),
|
|
21
|
+
...(whole && r.homeFloor !== undefined ? { floorSpelled: r.homeFloor } : {}),
|
|
22
|
+
...(whole && r.floorValues !== undefined ? { floors: r.floorValues } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function roleReads(r, provenance, value, role, globs, ctx, alsoOperand = false) {
|
|
26
|
+
if (role.kind === "program-text")
|
|
27
|
+
return alsoOperand ? [] : programTextReads(r, provenance, value, role.language, ctx);
|
|
28
|
+
if (role.kind === "object-path")
|
|
29
|
+
return [bare(r, provenance, value), ...objectPathsOf(value).map((c) => ({ ...bare(r, "object_path", c), denyOnly: true }))];
|
|
30
|
+
const named = slotValueCandidates(value, role, globs);
|
|
31
|
+
if (named === undefined)
|
|
32
|
+
return [{ ...bare(r, provenance), expands: "unresolvable" }];
|
|
33
|
+
if (role.kind === "mount-source")
|
|
34
|
+
return named.flatMap((c) => [bare(r, "mount_source", c), bare(r, "traversal_root", c)]);
|
|
35
|
+
return named.map((c) => bare(r, provenance, c));
|
|
36
|
+
}
|
|
37
|
+
function programTextReads(r, provenance, text, language, ctx) {
|
|
38
|
+
ctx.programTextSeen = true;
|
|
39
|
+
const opens = programTextOpensOf(language);
|
|
40
|
+
if (opens === "arithmetic") {
|
|
41
|
+
ctx.reentered.push(...arithmeticSubstitutions(text, ctx.table));
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
if (opens === "parsed" || opens === "never")
|
|
45
|
+
return [];
|
|
46
|
+
if (programTextMayOpen(language, text))
|
|
47
|
+
ctx.programText ??= programTextSentence(language);
|
|
48
|
+
return [{ ...bare(r, provenance, text), denyOnly: true }];
|
|
49
|
+
}
|
|
50
|
+
export function pushAssignment(reads, w, provenance) {
|
|
51
|
+
const a = w.assignment;
|
|
52
|
+
if (a === undefined || a.form === "compound")
|
|
53
|
+
return;
|
|
54
|
+
const expandsTilde = a.value.expansions.some((p) => p.expansion?.kind === "tilde");
|
|
55
|
+
const value = expandsTilde ? a.value.folded : literalTildeSpelling(a.value.folded);
|
|
56
|
+
const settled = a.op === "=" && !expandsTilde;
|
|
57
|
+
const named = a.value.folded === "" || (a.op === "+=" ? expandsTilde : !settled) ? undefined : value;
|
|
58
|
+
if (a.value.folded === "" && settled)
|
|
59
|
+
return;
|
|
60
|
+
const v = { spelled: value, raw: a.bare ? a.value.raw : w.raw, provenance };
|
|
61
|
+
const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? { ...v, expands: expandsOf(a.value) } : undefined;
|
|
62
|
+
if (!settled && unsettled !== undefined)
|
|
63
|
+
reads.push(unsettled);
|
|
64
|
+
const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
|
|
65
|
+
if (named !== undefined && !substitutes)
|
|
66
|
+
reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
|
|
67
|
+
if (a.value.folded.includes(":"))
|
|
68
|
+
for (const m of a.value.folded.split(":"))
|
|
69
|
+
if (m !== "") {
|
|
70
|
+
const r = readOfText(m);
|
|
71
|
+
reads.push({ spelled: r.folded, raw: r.raw, provenance, expands: r.expands, denyOnly: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function unlistedWords(reads, toks, from, launcherIndices, provenance, ctx, program) {
|
|
75
|
+
const argv = program !== undefined ? { program: program.basename, from: program.index } : argvProgramOf(toks.map((r) => r.word));
|
|
76
|
+
const argvStart = argv?.from ?? toks.length;
|
|
77
|
+
const slotVerb = argv?.program;
|
|
78
|
+
const slotRow = slotVerb === undefined ? undefined : programRow(slotVerb);
|
|
79
|
+
const noRow = slotVerb !== undefined && slotRow === undefined;
|
|
80
|
+
const speculate = (start, how) => {
|
|
81
|
+
if (!noRow)
|
|
82
|
+
return;
|
|
83
|
+
for (let i = start; i < reads.length; i++) {
|
|
84
|
+
const c = reads[i];
|
|
85
|
+
if (c.expands === "unresolvable" || c.speculative !== undefined || c.denyOnly === true || c.provenance === "program_path" || c.provenance === "env_assignment")
|
|
86
|
+
continue;
|
|
87
|
+
reads[i] = { ...c, speculative: how };
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const message = slotVerb === undefined || argv === undefined ? new Set() : messageWords(slotVerb, toks, argvStart);
|
|
91
|
+
let declaredNotPaths = assignmentArgumentsAreNotPaths(slotVerb ?? "");
|
|
92
|
+
const noPathOperands = slotVerb !== undefined && opensNoOperand(slotVerb);
|
|
93
|
+
const expanding = new Set();
|
|
94
|
+
for (let j = from; j < toks.length; j++) {
|
|
95
|
+
if (launcherIndices.has(j))
|
|
96
|
+
continue;
|
|
97
|
+
if (noPathOperands && j > argvStart && !evaluatesOperands(slotVerb))
|
|
98
|
+
break;
|
|
99
|
+
if (toks[j].expands !== "unresolvable")
|
|
100
|
+
continue;
|
|
101
|
+
expanding.add(j);
|
|
102
|
+
reads.push(bare(toks[j], j === argvStart ? "program_path" : provenance));
|
|
103
|
+
if (j < argvStart)
|
|
104
|
+
pushAssignment(reads, toks[j].word, "env_assignment");
|
|
105
|
+
else if (j > argvStart && !declaredNotPaths)
|
|
106
|
+
pushAssignment(reads, toks[j].word, provenance);
|
|
107
|
+
}
|
|
108
|
+
const positional = [];
|
|
109
|
+
const pastOperand = new Set();
|
|
110
|
+
let stdinOperand = false;
|
|
111
|
+
let globs = "expanded";
|
|
112
|
+
let walkRow = slotRow;
|
|
113
|
+
let walkFrom = argvStart + 1;
|
|
114
|
+
let relocated = false;
|
|
115
|
+
if (argvStart >= toks.length) {
|
|
116
|
+
let endOfOptions = false;
|
|
117
|
+
for (let j = from; j < toks.length; j++) {
|
|
118
|
+
if (launcherIndices.has(j) || expanding.has(j) || message.has(j))
|
|
119
|
+
continue;
|
|
120
|
+
const r = toks[j];
|
|
121
|
+
const word = r.folded;
|
|
122
|
+
if (j > from && !endOfOptions && word === "--") {
|
|
123
|
+
endOfOptions = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!endOfOptions && word.startsWith("-") && word !== "-") {
|
|
127
|
+
for (const payload of attachedOptionPayloads(word))
|
|
128
|
+
reads.push(bare(r, provenance, payload));
|
|
129
|
+
if (isPathShapedToken(word))
|
|
130
|
+
reads.push(bare(r, provenance));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
reads.push(bare(r, provenance));
|
|
134
|
+
pushAssignment(reads, r.word, "env_assignment");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (!noPathOperands) {
|
|
138
|
+
const programWord = toks[argvStart];
|
|
139
|
+
if (!expanding.has(argvStart) && !launcherIndices.has(argvStart) && !message.has(argvStart)) {
|
|
140
|
+
const word = programWord.folded;
|
|
141
|
+
if (isPathShapedToken(word)) {
|
|
142
|
+
if (word.startsWith("-") && word !== "-") {
|
|
143
|
+
for (const payload of attachedOptionPayloads(word))
|
|
144
|
+
reads.push(bare(programWord, provenance, payload));
|
|
145
|
+
reads.push(bare(programWord, provenance));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
reads.push(bare(programWord, "program_path"));
|
|
149
|
+
if (!declaredNotPaths)
|
|
150
|
+
pushAssignment(reads, programWord.word, provenance);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const args = toks.slice(argvStart + 1).map((r) => r.folded);
|
|
155
|
+
const sub = pathSubcommandOf(slotRow, args);
|
|
156
|
+
const tail = moduleTailOf(slotRow, args);
|
|
157
|
+
const skipped = (j) => launcherIndices.has(j) || expanding.has(j) || message.has(j) || (tail !== undefined && j >= argvStart + 1 + tail.from);
|
|
158
|
+
const walk = (row, first, last, slotKind) => {
|
|
159
|
+
const at = (k) => first + k;
|
|
160
|
+
const words = toks.slice(first, last).map((r, k) => ({ folded: r.folded, skip: skipped(at(k)) }));
|
|
161
|
+
globs = valuesAreLiteral(row, words.map((w) => w.folded)) ? "literal" : "expanded";
|
|
162
|
+
const someRead = wordsReadBySomeImplementations(row, words.map((w) => w.folded));
|
|
163
|
+
for (const e of readArgs(row, words, { firstOperandSlot: slotKind })) {
|
|
164
|
+
const start = reads.length;
|
|
165
|
+
handleUnlistedEvent(e, at(e.at));
|
|
166
|
+
if (someRead.has(e.at))
|
|
167
|
+
for (let i = start; i < reads.length; i++)
|
|
168
|
+
if (reads[i].expands !== "unresolvable" && reads[i].denyOnly !== true)
|
|
169
|
+
reads[i] = { ...reads[i], speculative: "containment" };
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
if (sub !== undefined) {
|
|
173
|
+
walk(slotRow, argvStart + 1, argvStart + 1 + sub.at, false);
|
|
174
|
+
declaredNotPaths = sub.row.operands?.assignmentArguments === "not-paths";
|
|
175
|
+
walkRow = sub.row;
|
|
176
|
+
walkFrom = argvStart + 2 + sub.at;
|
|
177
|
+
relocated = sub.relocated;
|
|
178
|
+
positional.length = 0;
|
|
179
|
+
stdinOperand = false;
|
|
180
|
+
walk(sub.row, walkFrom, toks.length, true);
|
|
181
|
+
}
|
|
182
|
+
else
|
|
183
|
+
walk(slotRow, argvStart + 1, toks.length, "program-text");
|
|
184
|
+
if (tail !== undefined)
|
|
185
|
+
for (let j = argvStart + 1 + tail.from; j < toks.length; j++)
|
|
186
|
+
if (!expanding.has(j))
|
|
187
|
+
reads.push(...roleReads(toks[j], "operand", toks[j].folded, tail.role, globs, ctx));
|
|
188
|
+
}
|
|
189
|
+
function handleUnlistedEvent(e, j) {
|
|
190
|
+
const r = toks[j];
|
|
191
|
+
const start = reads.length;
|
|
192
|
+
switch (e.kind) {
|
|
193
|
+
case "end-of-options":
|
|
194
|
+
return;
|
|
195
|
+
case "dash-word-operand":
|
|
196
|
+
pastOperand.add(j);
|
|
197
|
+
reads.push(bare(r, provenance));
|
|
198
|
+
speculate(start, "whole");
|
|
199
|
+
return;
|
|
200
|
+
case "value": {
|
|
201
|
+
if (!e.consumed) {
|
|
202
|
+
if (r.expands !== "unresolvable")
|
|
203
|
+
reads.push(...roleReads(r, "option_value", r.folded, e.role, globs, ctx, true));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (expanding.has(j))
|
|
207
|
+
return;
|
|
208
|
+
const named = roleReads(r, "option_value", r.folded, e.role, globs, ctx);
|
|
209
|
+
reads.push(...named);
|
|
210
|
+
if (named.some((c) => c.denyOnly !== true))
|
|
211
|
+
positional.push(r);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
case "option":
|
|
215
|
+
if (e.reading === "no-value")
|
|
216
|
+
return;
|
|
217
|
+
if (e.reading === "speculative") {
|
|
218
|
+
for (const payload of attachedOptionPayloads(r.folded))
|
|
219
|
+
reads.push(bare(r, provenance, payload));
|
|
220
|
+
speculate(start, "containment");
|
|
221
|
+
const whole = reads.length;
|
|
222
|
+
if (isPathShapedToken(r.folded))
|
|
223
|
+
reads.push(bare(r, provenance));
|
|
224
|
+
speculate(whole, "whole");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
reads.push(...roleReads(r, "option_value", literalTildeSpelling(e.reading.attached), e.reading.role, globs, ctx, pastOperand.has(j)));
|
|
228
|
+
return;
|
|
229
|
+
case "operand": {
|
|
230
|
+
const role = e.dash === undefined ? (e.slot ?? e.role) : undefined;
|
|
231
|
+
const named = role !== undefined ? roleReads(r, provenance, r.folded, role, globs, ctx) : [bare(r, provenance)];
|
|
232
|
+
if (e.dash === "stdin")
|
|
233
|
+
stdinOperand = true;
|
|
234
|
+
else if (named.some((c) => c.denyOnly !== true))
|
|
235
|
+
positional.push(r);
|
|
236
|
+
reads.push(...named);
|
|
237
|
+
speculate(start, r.word.assignment !== undefined ? "whole" : "containment");
|
|
238
|
+
const values = reads.length;
|
|
239
|
+
if (!declaredNotPaths && e.slot === undefined)
|
|
240
|
+
pushAssignment(reads, r.word, provenance);
|
|
241
|
+
speculate(values, "containment");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (walkRow !== undefined && argv !== undefined && selectsContentsTraversal(walkRow, toks.slice(walkFrom).map((x) => x.folded))) {
|
|
247
|
+
const roots = positional.length > 0 ? positional : stdinOperand || !walksCwdWithoutOperand(walkRow) ? [] : [readOfText(".")];
|
|
248
|
+
for (const root of roots)
|
|
249
|
+
reads.push(relocated ? { ...bare(root, "traversal_root"), expands: "unresolvable" } : bare(root, "traversal_root"));
|
|
250
|
+
}
|
|
251
|
+
for (let j = from; j < toks.length; j++) {
|
|
252
|
+
if (launcherIndices.has(j) || j === argvStart)
|
|
253
|
+
continue;
|
|
254
|
+
if (noPathOperands && j > argvStart)
|
|
255
|
+
break;
|
|
256
|
+
const start = reads.length;
|
|
257
|
+
if (toks[j].expands === "glob")
|
|
258
|
+
reads.push(bare(toks[j], provenance));
|
|
259
|
+
speculate(start, "containment");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export function listedVerbCandidates(name, argReads, reads, ctx) {
|
|
263
|
+
if (opensNoOperand(name))
|
|
264
|
+
return;
|
|
265
|
+
const row = programRow(name);
|
|
266
|
+
const args = argReads.map((r) => r.folded);
|
|
267
|
+
const expanding = new Set();
|
|
268
|
+
argReads.forEach((r, k) => {
|
|
269
|
+
if (!rewritten(r))
|
|
270
|
+
return;
|
|
271
|
+
expanding.add(k);
|
|
272
|
+
reads.push({ ...bare(r, "operand"), expands: "unresolvable" });
|
|
273
|
+
});
|
|
274
|
+
const sub = pathSubcommandOf(row, args);
|
|
275
|
+
const tail = moduleTailOf(row, args);
|
|
276
|
+
if (sub !== undefined) {
|
|
277
|
+
walkListed(row, argReads, 0, sub.at, expanding, reads, ctx, false);
|
|
278
|
+
walkListed(sub.row, argReads, sub.at + 1, argReads.length, expanding, reads, ctx, sub.relocated);
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
walkListed(row, argReads, 0, tail !== undefined ? tail.from : argReads.length, expanding, reads, ctx, false);
|
|
282
|
+
if (tail !== undefined)
|
|
283
|
+
for (let k = tail.from; k < argReads.length; k++)
|
|
284
|
+
if (!expanding.has(k))
|
|
285
|
+
reads.push(...roleReads(argReads[k], "operand", args[k], tail.role, "expanded", ctx));
|
|
286
|
+
for (let k = 0; k < args.length; k++) {
|
|
287
|
+
if (expanding.has(k))
|
|
288
|
+
continue;
|
|
289
|
+
if (argReads[k].expands === "glob")
|
|
290
|
+
reads.push(bare(argReads[k], "operand"));
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function walkListed(row, argReads, first, last, expanding, reads, ctx, relocated) {
|
|
294
|
+
const slice = argReads.slice(first, last);
|
|
295
|
+
const args = slice.map((r) => r.folded);
|
|
296
|
+
const positional = [];
|
|
297
|
+
let stdinOperand = false;
|
|
298
|
+
const globs = valuesAreLiteral(row, args) ? "literal" : "expanded";
|
|
299
|
+
const someRead = wordsReadBySomeImplementations(row, args);
|
|
300
|
+
const pastOperand = new Set();
|
|
301
|
+
const offer = (k, value, role, provenance, alsoOperand = false) => {
|
|
302
|
+
const named = roleReads(slice[k], provenance, value, role, globs, ctx, alsoOperand);
|
|
303
|
+
reads.push(...named);
|
|
304
|
+
return named.filter((c) => c.denyOnly !== true).length;
|
|
305
|
+
};
|
|
306
|
+
for (const e of readArgs(row, args.map((folded, k) => ({ folded, skip: expanding.has(first + k) })), { firstOperandSlot: true })) {
|
|
307
|
+
const t = args[e.at];
|
|
308
|
+
const start = reads.length;
|
|
309
|
+
switch (e.kind) {
|
|
310
|
+
case "end-of-options":
|
|
311
|
+
break;
|
|
312
|
+
case "dash-word-operand":
|
|
313
|
+
pastOperand.add(e.at);
|
|
314
|
+
reads.push(bare(slice[e.at], "operand"));
|
|
315
|
+
break;
|
|
316
|
+
case "value": {
|
|
317
|
+
if (expanding.has(first + e.at))
|
|
318
|
+
break;
|
|
319
|
+
if (offer(e.at, t, e.role, "option_value", !e.consumed) > 0 && e.consumed)
|
|
320
|
+
positional.push(slice[e.at]);
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case "option":
|
|
324
|
+
if (e.reading === "no-value")
|
|
325
|
+
break;
|
|
326
|
+
if (e.reading === "speculative") {
|
|
327
|
+
for (const payload of attachedOptionPayloads(t))
|
|
328
|
+
reads.push(bare(slice[e.at], "option_value", payload));
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
offer(e.at, literalTildeSpelling(e.reading.attached), e.reading.role, "option_value", pastOperand.has(e.at));
|
|
332
|
+
break;
|
|
333
|
+
case "operand": {
|
|
334
|
+
if (e.dash !== undefined) {
|
|
335
|
+
if (e.slot !== undefined)
|
|
336
|
+
break;
|
|
337
|
+
if (e.dash === "stdin")
|
|
338
|
+
stdinOperand = true;
|
|
339
|
+
else
|
|
340
|
+
positional.push(slice[e.at]);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
if (e.slot !== undefined) {
|
|
344
|
+
if (offer(e.at, t, e.slot, "operand") > 0)
|
|
345
|
+
positional.push(slice[e.at]);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
let named = 1;
|
|
349
|
+
if (e.role !== undefined)
|
|
350
|
+
named = offer(e.at, t, e.role, "operand");
|
|
351
|
+
else
|
|
352
|
+
reads.push(bare(slice[e.at], "operand"));
|
|
353
|
+
if (named > 0)
|
|
354
|
+
positional.push(slice[e.at]);
|
|
355
|
+
if (row?.operands?.assignmentArguments !== "not-paths")
|
|
356
|
+
pushAssignment(reads, slice[e.at].word, "operand");
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (someRead.has(e.at))
|
|
361
|
+
for (let i = start; i < reads.length; i++)
|
|
362
|
+
if (reads[i].expands !== "unresolvable" && reads[i].denyOnly !== true)
|
|
363
|
+
reads[i] = { ...reads[i], speculative: "containment" };
|
|
364
|
+
}
|
|
365
|
+
if (selectsContentsTraversal(row, args)) {
|
|
366
|
+
const roots = positional.length > 0 ? positional : stdinOperand || !walksCwdWithoutOperand(row) ? [] : [readOfText(".")];
|
|
367
|
+
for (const root of roots)
|
|
368
|
+
reads.push(relocated ? { ...bare(root, "traversal_root"), expands: "unresolvable" } : bare(root, "traversal_root"));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
export function subscriptOnly(word) {
|
|
372
|
+
const at = word.indexOf("[");
|
|
373
|
+
return at < 0 ? "" : word.slice(at);
|
|
374
|
+
}
|
|
375
|
+
export function assignmentArithmetic(w, integer, ctx, asWritten = false) {
|
|
376
|
+
const a = w.assignment;
|
|
377
|
+
if (a === undefined) {
|
|
378
|
+
if (!asWritten)
|
|
379
|
+
ctx.reentered.push(...arithmeticSubstitutions(subscriptOnly(w.folded), ctx.table));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (a.subscript !== undefined && !asWritten)
|
|
383
|
+
ctx.reentered.push(...arithmeticSubstitutions(`[${a.subscript.folded}]`, ctx.table));
|
|
384
|
+
if (integer)
|
|
385
|
+
ctx.reentered.push(...arithmeticSubstitutions(a.value.folded, ctx.table));
|
|
386
|
+
}
|