@sema-agent/core 7.44.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 +418 -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 +65 -38
- package/dist/agents/tool-filter.d.ts +82 -5
- package/dist/agents/tool-filter.js +134 -4
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/agent-definition.d.ts +2 -2
- package/dist/core/allow-entry.d.ts +30 -4
- package/dist/core/allow-entry.js +32 -5
- package/dist/core/ask-origin.d.ts +23 -4
- package/dist/core/ask-origin.js +11 -5
- package/dist/core/checkpoint-store.d.ts +41 -4
- package/dist/core/decision-receipt.js +1 -1
- package/dist/core/effective-path-target.d.ts +143 -10
- package/dist/core/effective-path-target.js +113 -9
- package/dist/core/engine-notice.d.ts +25 -3
- package/dist/core/engine-notice.js +12 -6
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +5 -0
- package/dist/core/gate-outcome.js +2 -0
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +5 -0
- package/dist/core/memory-engine/content-origin.d.ts +0 -34
- package/dist/core/memory-engine/content-origin.js +9 -4
- package/dist/core/memory-engine/layout.d.ts +5 -1
- package/dist/core/memory-engine/layout.js +1 -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 -36
- package/dist/core/permission-rules.js +50 -40
- package/dist/core/real-approval.d.ts +9 -6
- package/dist/core/real-approval.js +5 -5
- package/dist/core/runner/child-tree.d.ts +110 -0
- package/dist/core/runner/child-tree.js +107 -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 +54 -2
- 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 +6 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-config-doors.js +3 -1
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +11 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -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-listings.d.ts +4 -4
- package/dist/core/runner/prepare-listings.js +2 -22
- package/dist/core/runner/prepare-park-ask.d.ts +5 -0
- package/dist/core/runner/prepare-park-ask.js +6 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +16 -2
- package/dist/core/runner/prepare-policy-chain.js +14 -5
- 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 +41 -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/resume-policy-outcome.js +2 -1
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/runtask.d.ts +2 -0
- package/dist/core/runner/runtask.js +4 -2
- package/dist/core/runner/stream-steer-verb.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +32 -0
- package/dist/core/runner/sub-agent-deny-lists.js +121 -0
- package/dist/core/runner/synthetic-tools.d.ts +1 -1
- package/dist/core/runner/synthetic-tools.js +3 -2
- 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/session-policy-store.d.ts +6 -2
- package/dist/core/session-policy-store.js +2 -1
- 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/skill-grant.d.ts +10 -2
- package/dist/core/skill-grant.js +20 -4
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/task-notification.d.ts +12 -1
- package/dist/core/task-notification.js +13 -1
- package/dist/core/task-spec.d.ts +5 -3
- 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 +45 -12
- package/dist/core/tool-policy.js +18 -20
- package/dist/core/tool-spec.d.ts +23 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +10 -6
- package/dist/index.js +9 -5
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +3 -2
- 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 +83 -0
- package/dist/tools/fs/removal-targets.js +236 -0
- 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 -37
- 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 +49 -3
- package/wire-changes/core@7.45.0.json +1084 -0
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -5,7 +5,8 @@ import { combinePolicies } from "../core/tool-policy.js";
|
|
|
5
5
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
6
6
|
import { isDurablePause } from "../agents/suspend-guard.js";
|
|
7
7
|
import { terminalProjection } from "../core/runner/terminal-projection.js";
|
|
8
|
-
import {
|
|
8
|
+
import { writeTargetPath } from "../tools/fs/safety.js";
|
|
9
|
+
import { callTargetViewsOf } from "../core/effective-path-target.js";
|
|
9
10
|
import { captureBaseline, resolveFrozenPaths, restoreFrozenPaths, runOracle, snapshotFrozenPaths, specError, } from "../core/spec-contract.js";
|
|
10
11
|
import { isProtectedWrite } from "../core/tool-registry.js";
|
|
11
12
|
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
|
|
@@ -21,13 +22,13 @@ export function frozenDenyPolicy(rootDir, frozenResolved) {
|
|
|
21
22
|
check(req) {
|
|
22
23
|
if (!isProtectedWrite(req))
|
|
23
24
|
return { action: "allow" };
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
25
|
+
const views = callTargetViewsOf(req, "tighten", { root: rootDir });
|
|
26
|
+
if (views === undefined || views.spellings.length === 0)
|
|
26
27
|
return { action: "allow" };
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
return { action: "deny", message: `${raw} is part of the frozen specification surface (read-only).` };
|
|
28
|
+
const raw = writeTargetPath(req);
|
|
29
|
+
const hit = views.spellings.some((v) => frozen.has(pathKey(v)));
|
|
30
|
+
if (hit) {
|
|
31
|
+
return { action: "deny", message: `${typeof raw === "string" && raw.length > 0 ? raw : views.spellings[0]} is part of the frozen specification surface (read-only).` };
|
|
31
32
|
}
|
|
32
33
|
return { action: "allow" };
|
|
33
34
|
},
|
|
@@ -19,6 +19,7 @@ import { boundInputHashOf } from "../core/canonical-json.js";
|
|
|
19
19
|
import { boundedRedactedSummary } from "../core/untrusted-egress.js";
|
|
20
20
|
import { delimitUntrusted } from "../core/untrusted-text.js";
|
|
21
21
|
import { OUTPUT_TOOL_NAME } from "../core/runner/synthetic-tools.js";
|
|
22
|
+
import { deniedToolNamesOf } from "../core/runner/sub-agent-deny-lists.js";
|
|
22
23
|
import { compileOutputSchema } from "../core/runner/strict-output-schema.js";
|
|
23
24
|
import { WorkflowBudgetExceededError, WorkflowNestingError, WorkflowAgentBlockedError, WorkflowAgentParkedError, WorkflowAgentSchemaError, WorkflowAgentStalledError, WorkflowMaxAgentsError, WORKFLOW_SPAWN_BLOCKED_ERROR_CODE } from "./workflow-types.js";
|
|
24
25
|
import { WorkflowScriptError } from "./workflow-meta.js";
|
|
@@ -118,7 +119,7 @@ export function applyWorkflowAgentType(spec, agentType, registry) {
|
|
|
118
119
|
if (out.memoryPersistenceCapable === undefined && def.memoryPersistenceCapable !== undefined)
|
|
119
120
|
out.memoryPersistenceCapable = def.memoryPersistenceCapable;
|
|
120
121
|
if (def.allowTools !== undefined || def.denyTools !== undefined) {
|
|
121
|
-
const perType = createAllowDenyPolicy({ ...(def.allowTools ? { allow: def.allowTools } : {}), ...(def.denyTools ? { deny: def.denyTools } : {}), scope: "narrowing" });
|
|
122
|
+
const perType = createAllowDenyPolicy({ ...(def.allowTools ? { allow: def.allowTools } : {}), ...(def.denyTools ? { deny: deniedToolNamesOf(`workflow agentType "${def.name}"`, def.denyTools) } : {}), scope: "narrowing" });
|
|
122
123
|
out.toolPolicy = out.toolPolicy ? combinePolicies(out.toolPolicy, perType) : perType;
|
|
123
124
|
}
|
|
124
125
|
return out;
|
|
@@ -1578,7 +1579,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
|
|
|
1578
1579
|
opts.onForwardEvent(e.type === "task_progress" ? { ...e, workflowRunId: runId, workflowAgentLabel: label } : e);
|
|
1579
1580
|
}
|
|
1580
1581
|
: undefined;
|
|
1581
|
-
const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd } : {}), ...spawnAttribution, ...(await captureFloorSeatsNow()), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
|
|
1582
|
+
const baseInternals = { ...(agentOpts.isolation ? { isolation: agentOpts.isolation } : {}), ...(opts.parentCwd !== undefined ? { parentCwd: opts.parentCwd, callerRoot: opts.parentCwd } : {}), ...spawnAttribution, ...(await captureFloorSeatsNow()), ...(enrichedForward !== undefined ? { onForwardEvent: enrichedForward } : {}), delegationTaskType: "workflow", agentName: label, onWorkspaceResolved: createWorkspaceObserver(rec) };
|
|
1582
1583
|
let attempts = 0;
|
|
1583
1584
|
let throttleRetried = false;
|
|
1584
1585
|
let lastAttemptReason;
|
package/dist/shell/lex.d.ts
CHANGED
|
@@ -248,6 +248,13 @@ export declare function substitutionsInHeredocBody(body: string): {
|
|
|
248
248
|
readonly body: string;
|
|
249
249
|
readonly kind: "command" | "arithmetic";
|
|
250
250
|
}[] | undefined;
|
|
251
|
+
/** The text a word's pieces hand the program when every piece is literal; `undefined` when one is an expansion or
|
|
252
|
+
* unresolved. */
|
|
253
|
+
export declare function literalTextOf(pieces: readonly WordPiece[]): string | undefined;
|
|
254
|
+
/** The text an UNQUOTED here-document body delivers when the shell expands nothing in it: a backslash before `$`, a
|
|
255
|
+
* backquote or a backslash is removed (a line continuation was joined when the body was read), every other character
|
|
256
|
+
* stands. `undefined` when an unescaped `$` or backquote stands in it: the program reads what the expansion makes. */
|
|
257
|
+
export declare function literalHeredocBody(body: string): string | undefined;
|
|
251
258
|
/** Lex a command: its segments, or the one reason bash (or this lexer) cannot delimit them. */
|
|
252
259
|
export declare function lexShell(source: string): ShellLex;
|
|
253
260
|
/**
|
package/dist/shell/lex.js
CHANGED
|
@@ -319,6 +319,23 @@ export function substitutionsInHeredocBody(body) {
|
|
|
319
319
|
}
|
|
320
320
|
return out;
|
|
321
321
|
}
|
|
322
|
+
export function literalTextOf(pieces) {
|
|
323
|
+
return pieces.every((q) => q.literal !== undefined) ? pieces.map((q) => q.literal).join("") : undefined;
|
|
324
|
+
}
|
|
325
|
+
export function literalHeredocBody(body) {
|
|
326
|
+
let out = "";
|
|
327
|
+
for (let i = 0; i < body.length; i++) {
|
|
328
|
+
const ch = body[i];
|
|
329
|
+
if (ch === "$" || ch === "`")
|
|
330
|
+
return undefined;
|
|
331
|
+
if (ch === "\\" && i + 1 < body.length && "$`\\".includes(body[i + 1])) {
|
|
332
|
+
out += body[++i];
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
out += ch;
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
322
339
|
const BLANK = /[ \t]/;
|
|
323
340
|
const fail = (reason, at) => ({ parseFailure: { reason, at } });
|
|
324
341
|
const publicPiece = (p) => ({
|
|
@@ -31,8 +31,6 @@ export declare function writtenAssignmentPrefix(token: string): {
|
|
|
31
31
|
readonly settlesWholeName: boolean;
|
|
32
32
|
} | undefined;
|
|
33
33
|
export { SHELL_CONTROL_PREFIXES };
|
|
34
|
-
/** Shell block words that name no command of their own (a loop head, a block close, a case head). */
|
|
35
|
-
export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
|
|
36
34
|
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
|
|
37
35
|
* looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
|
|
38
36
|
export declare function isBareControlWord(words: readonly LexedWord[], i: number, set: ReadonlySet<string>): boolean;
|
|
@@ -43,88 +41,6 @@ export declare function isBareControlWord(words: readonly LexedWord[], i: number
|
|
|
43
41
|
* over control words and earlier assignments re-traces). One fact, two readers that must agree.
|
|
44
42
|
*/
|
|
45
43
|
export declare function isLeadingAssignmentWord(words: readonly LexedWord[], i: number): boolean;
|
|
46
|
-
/**
|
|
47
|
-
* Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
|
|
48
|
-
*
|
|
49
|
-
* The same class as the shell re-entry programs, one level down: there the unseen text is COMMANDS; here the
|
|
50
|
-
* command's name IS visible and its argv is not — `echo <path> | xargs cat` opens a file no token of this walk
|
|
51
|
-
* ever named, because the operand arrives on a stream. The membership criterion is that behaviour and only that
|
|
52
|
-
* behaviour; a program that consumes stdin as DATA (`sort`, `wc`, a reader the deployment never listed) is NOT a
|
|
53
|
-
* member, because its stdin never becomes a filename. Spelled as a pattern — these are program names, never tool
|
|
54
|
-
* names.
|
|
55
|
-
*
|
|
56
|
-
* "EXECS DIRECTLY" is what separates this class from the shell re-entry class, and it is why GNU `parallel` is
|
|
57
|
-
* NOT a member: parallel BUILDS A COMMAND LINE and hands it to a shell, and with no command word at all it runs
|
|
58
|
-
* each input line as a command — so parallel belongs to the shell re-entry class and is refused whole with no
|
|
59
|
-
* exemption at all. A spelling this file has no name for (an aliased or wrapped `xargs`) is judged as any other
|
|
60
|
-
* unlisted program is — the name-table floor the re-entry class records for itself.
|
|
61
|
-
*/
|
|
62
|
-
export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
|
|
63
|
-
/** The program an {@link ARGV_REENTRY_PROGRAM_RE} launcher runs when the chain names none: `xargs` defaults to
|
|
64
|
-
* `echo`, which is why a bare one reads nothing. */
|
|
65
|
-
export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
|
|
66
|
-
/**
|
|
67
|
-
* What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
|
|
68
|
-
* option whose value is the next token. Every spelling not in the map falls to the refusal (the conservative side,
|
|
69
|
-
* and the verdict those spellings always had); a `1` with no token left to take is not readable either.
|
|
70
|
-
*
|
|
71
|
-
* THREE SPELLINGS OF ONE DECLARATION, because getopt has three: an arity-1 entry is honoured separated (`-k 5`),
|
|
72
|
-
* fused to its long name (`--signal=TERM`) and fused to its short letter (`-k5`), and an arity-0 short entry inside
|
|
73
|
-
* a cluster of arity-0 letters (`-pv`). Reading only the exact spelling made one program two.
|
|
74
|
-
*
|
|
75
|
-
* WHAT A ROW MAY NOT GET WRONG: declaring an arity-1 option as arity 0 steps onto its VALUE and reads the value as
|
|
76
|
-
* the program — the one direction that turns a refusal into a wrong answer rather than a lost one. A row carries
|
|
77
|
-
* only options whose arity every supported implementation agrees on; a flag no row declares still refuses.
|
|
78
|
-
*/
|
|
79
|
-
export interface LauncherRow {
|
|
80
|
-
/** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
|
|
81
|
-
* (`time -p …`) or the written command word (`"time" -p …`, `X=1 time -p …`, `/usr/bin/time -p …`). A launcher
|
|
82
|
-
* nested behind another (`env time -p …`) is that launcher's operand, and its options refuse: resolving past
|
|
83
|
-
* them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
|
|
84
|
-
* table only ever finds MORE program to judge. */
|
|
85
|
-
readonly optionArity: ReadonlyMap<string, 0 | 1>;
|
|
86
|
-
/** A bare NEGATIVE integer in this launcher's option section is its OWN argument (`nice -5 <cmd>`), not a flag
|
|
87
|
-
* the table cannot read. Per row: elsewhere a dash-leading token is an option, and reading one as a number
|
|
88
|
-
* would step over a flag whose meaning this table does not know. */
|
|
89
|
-
readonly negativeIntegerIsOwnArgument?: true;
|
|
90
|
-
/** Positional operands the launcher takes BEFORE the program, consumed whatever they spell (`timeout DURATION
|
|
91
|
-
* COMMAND`); a spelling-based reading under-accepted, and every spelling it missed became the program word. */
|
|
92
|
-
readonly requiredOperands?: number;
|
|
93
|
-
/** The launcher reads NAME=VALUE operands with ITS OWN name grammar (`env 1=x …`), wider than the shell's. */
|
|
94
|
-
readonly assignmentOperands?: true;
|
|
95
|
-
/** The launcher hands its operands to a SHELL as text (`watch "cat /x"` runs `sh -c`): no program word to read. */
|
|
96
|
-
readonly shellText?: true;
|
|
97
|
-
/** With no program word the launcher runs a SHELL: piped text would reach it, so an empty program position refuses. */
|
|
98
|
-
readonly whenNoProgram?: "shell";
|
|
99
|
-
/** Options whose value is a WHOLE COMMAND LINE the launcher splits and runs (`env -S "cmd arg"`,
|
|
100
|
-
* `--split-string=…`). Not an arity this reader steps over — the program is inside the value, so the
|
|
101
|
-
* program-position walk still refuses here — but the candidate extraction reads the value as nested shell
|
|
102
|
-
* text instead of as one opaque word. */
|
|
103
|
-
readonly commandString?: readonly string[];
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
|
|
107
|
-
* sit behind one (`env cat <path>`, `sudo cat <path>`, `timeout 5 cat <path>`), each with the option grammar this
|
|
108
|
-
* file reads for it.
|
|
109
|
-
*
|
|
110
|
-
* Membership is the key set. A row with no declared options means every option-shaped word after that launcher
|
|
111
|
-
* refuses ("which program runs is not readable without the launcher's option table") — the answer every launcher
|
|
112
|
-
* but `time` has always had, now stated per row rather than by omission. Declaring a row's options is a behaviour
|
|
113
|
-
* decision with its own criteria; adding a launcher is a row.
|
|
114
|
-
*
|
|
115
|
-
* `time` carries both spellings a deployment can meet: the shell builtin takes `-p` alone, and the standalone
|
|
116
|
-
* program adds a format, an output file, and three switches.
|
|
117
|
-
*
|
|
118
|
-
* A deliberate SUBSET of the rule lane's own `BARE_INTERPRETER_NAMES`, mirrored rather than imported because that
|
|
119
|
-
* module imports the classifier — the dependency runs that way, and a cycle is a worse problem than a mirror whose
|
|
120
|
-
* subset relation is pinned by test. GNU `parallel` is absent for the reason the shells are absent: it does not run
|
|
121
|
-
* a program named in token-visible argv — it BUILDS a command line and hands it to a shell — so it is a shell
|
|
122
|
-
* re-entry member instead. Language interpreters and shells are excluded as a scope statement: their program
|
|
123
|
-
* argument is a script path or a quoted code string, not a token-visible program with token-visible operands.
|
|
124
|
-
*/
|
|
125
|
-
export declare const LAUNCHER_TABLE: ReadonlyMap<string, LauncherRow>;
|
|
126
|
-
/** The launcher names — derived from the table, never kept beside it. */
|
|
127
|
-
export declare const COMMAND_LAUNCHERS: ReadonlySet<string>;
|
|
128
44
|
export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"];
|
|
129
45
|
export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number];
|
|
130
46
|
/** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word
|
|
@@ -140,6 +56,9 @@ export type ProgramPositionRefusal = {
|
|
|
140
56
|
/** Not a DECLINE ({@link programPositionRunsShell}): the row WAS read and a SHELL stands at the program
|
|
141
57
|
* position. Absent ⇒ genuine control syntax, which IS one. */
|
|
142
58
|
readonly runsShell?: true;
|
|
59
|
+
/** The launcher hands the shell its operands as text (`watch "S"`); absent beside `runsShell`, the shell is handed
|
|
60
|
+
* nothing (`sudo -s`, `chroot DIR`) and runs its standard input. */
|
|
61
|
+
readonly handsText?: true;
|
|
143
62
|
};
|
|
144
63
|
export interface ProgramPosition {
|
|
145
64
|
/** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
|
|
@@ -159,6 +78,8 @@ export interface ProgramPosition {
|
|
|
159
78
|
readonly argvFromStdin: boolean;
|
|
160
79
|
/** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */
|
|
161
80
|
readonly runsDefaultProgram: boolean;
|
|
81
|
+
/** The program the stream-fed chain member runs when the chain names none (`xargs` runs `echo`). */
|
|
82
|
+
readonly defaultProgram?: string;
|
|
162
83
|
/** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what
|
|
163
84
|
* bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none or the read was refused. */
|
|
164
85
|
readonly program: {
|
|
@@ -184,7 +105,7 @@ export declare function readProgramPosition(words: readonly LexedWord[]): Progra
|
|
|
184
105
|
/** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
|
|
185
106
|
* stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
|
|
186
107
|
* consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
|
|
187
|
-
export declare function argvReentryUnjudged(pos: ProgramPosition,
|
|
108
|
+
export declare function argvReentryUnjudged(pos: ProgramPosition, acceptsTarget: (basename: string) => boolean): boolean;
|
|
188
109
|
/**
|
|
189
110
|
* The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a
|
|
190
111
|
* seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the
|
|
@@ -197,9 +118,9 @@ export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
|
|
|
197
118
|
/** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
|
|
198
119
|
* ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
|
|
199
120
|
* cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
|
|
200
|
-
export declare function programPositionRunsShell(pos: ProgramPosition,
|
|
121
|
+
export declare function programPositionRunsShell(pos: ProgramPosition, runsShell: (basename: string) => boolean): boolean;
|
|
201
122
|
/** Which decline, if any, this program position raises — ONE reading of the two facts above, so no consumer
|
|
202
123
|
* re-derives either. A refusal that says a SHELL runs is NOT one: it names what runs. */
|
|
203
|
-
export declare function shellScanDeclineOf(pos: ProgramPosition,
|
|
124
|
+
export declare function shellScanDeclineOf(pos: ProgramPosition, acceptsStreamTarget: (basename: string) => boolean): ShellScanDecline | undefined;
|
|
204
125
|
/** ONE sentence per refusal kind, minted here and nowhere else. */
|
|
205
126
|
export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SHELL_CONTROL_PREFIXES } from "../../shell/lex.js";
|
|
2
|
+
import { BLOCK_WORD_NAMES, LAUNCHER_NAMES, feedsArgvFromStdin, launcherOptionStep, programRow, selectedOptions, streamDefaultProgram } from "./program-read-model.js";
|
|
2
3
|
export const NOT_AUTO_ALLOWED = "— not auto-allowed";
|
|
3
4
|
export function programBasename(word) {
|
|
4
5
|
return word.includes("/") ? (word.split("/").pop() ?? word) : word;
|
|
@@ -14,7 +15,6 @@ export function writtenAssignmentPrefix(token) {
|
|
|
14
15
|
return { prefix, name: bracket < 0 ? written : written.slice(0, bracket), appends, settlesWholeName: !appends && bracket < 0 };
|
|
15
16
|
}
|
|
16
17
|
export { SHELL_CONTROL_PREFIXES };
|
|
17
|
-
export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
|
|
18
18
|
export function isBareControlWord(words, i, set) {
|
|
19
19
|
const w = words[i];
|
|
20
20
|
return w !== undefined && w.raw === w.folded && set.has(w.folded);
|
|
@@ -23,48 +23,19 @@ export function isLeadingAssignmentWord(words, i) {
|
|
|
23
23
|
const w = words[i];
|
|
24
24
|
return w !== undefined && w.assignment !== undefined && w.assignment.bare && w.atCommandPosition;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export const LAUNCHER_TABLE = new Map([
|
|
31
|
-
["env", launcherRow([["-i", 0], ["-0", 0], ["-v", 0], ["--ignore-environment", 0], ["--null", 0], ["--debug", 0], ["-u", 1], ["--unset", 1]], { assignmentOperands: true, commandString: ["-S", "--split-string"] })],
|
|
32
|
-
["command", launcherRow([["-p", 0]])],
|
|
33
|
-
["builtin", NO_DECLARED_OPTIONS], ["exec", NO_DECLARED_OPTIONS],
|
|
34
|
-
["noglob", NO_DECLARED_OPTIONS], ["xargs", NO_DECLARED_OPTIONS], ["busybox", NO_DECLARED_OPTIONS],
|
|
35
|
-
["sudo", { optionArity: new Map(), assignmentOperands: true }], ["doas", NO_DECLARED_OPTIONS], ["pkexec", { optionArity: new Map(), whenNoProgram: "shell" }],
|
|
36
|
-
["su", { optionArity: new Map(), shellText: true }], ["runuser", { optionArity: new Map(), shellText: true }],
|
|
37
|
-
["chroot", { optionArity: new Map(), requiredOperands: 1, whenNoProgram: "shell" }], ["setpriv", NO_DECLARED_OPTIONS],
|
|
38
|
-
["nohup", NO_DECLARED_OPTIONS],
|
|
39
|
-
["nice", launcherRow([["-n", 1], ["--adjustment", 1]], { negativeIntegerIsOwnArgument: true })],
|
|
40
|
-
["ionice", NO_DECLARED_OPTIONS], ["chrt", { optionArity: new Map(), requiredOperands: 1 }], ["taskset", { optionArity: new Map(), requiredOperands: 1 }],
|
|
41
|
-
["stdbuf", launcherRow([["-i", 1], ["-o", 1], ["-e", 1], ["--input", 1], ["--output", 1], ["--error", 1]])],
|
|
42
|
-
["timeout", launcherRow([
|
|
43
|
-
["--foreground", 0], ["--preserve-status", 0], ["--verbose", 0], ["-v", 0],
|
|
44
|
-
["-k", 1], ["-s", 1], ["--kill-after", 1], ["--signal", 1],
|
|
45
|
-
], { requiredOperands: 1 })],
|
|
46
|
-
["time", {
|
|
47
|
-
optionArity: new Map([
|
|
48
|
-
["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0], ["--", 0],
|
|
49
|
-
["-f", 1], ["-o", 1], ["--format", 1], ["--output", 1],
|
|
50
|
-
]),
|
|
51
|
-
}],
|
|
52
|
-
["watch", { optionArity: new Map(), shellText: true }], ["setsid", NO_DECLARED_OPTIONS], ["flock", { optionArity: new Map(), requiredOperands: 1 }], ["unshare", { optionArity: new Map(), whenNoProgram: "shell" }],
|
|
53
|
-
["nsenter", { optionArity: new Map(), whenNoProgram: "shell" }], ["script", { optionArity: new Map(), shellText: true }], ["numactl", NO_DECLARED_OPTIONS], ["prlimit", NO_DECLARED_OPTIONS],
|
|
54
|
-
["systemd-run", NO_DECLARED_OPTIONS],
|
|
55
|
-
["strace", NO_DECLARED_OPTIONS], ["ltrace", NO_DECLARED_OPTIONS], ["valgrind", NO_DECLARED_OPTIONS], ["firejail", { optionArity: new Map(), whenNoProgram: "shell" }],
|
|
56
|
-
["bwrap", NO_DECLARED_OPTIONS],
|
|
57
|
-
]);
|
|
58
|
-
export const COMMAND_LAUNCHERS = new Set(LAUNCHER_TABLE.keys());
|
|
26
|
+
function launcherRowOf(name) {
|
|
27
|
+
const row = programRow(name);
|
|
28
|
+
return row?.launcher !== undefined ? row : undefined;
|
|
29
|
+
}
|
|
59
30
|
function pastLauncherOptions(launcher, folded, i) {
|
|
60
|
-
const row =
|
|
31
|
+
const row = launcherRowOf(launcher);
|
|
61
32
|
if (row === undefined)
|
|
62
33
|
return i;
|
|
63
34
|
for (;;) {
|
|
64
35
|
const tok = folded[i];
|
|
65
36
|
if (tok === undefined)
|
|
66
37
|
return i;
|
|
67
|
-
if (row.negativeIntegerIsOwnArgument === true && /^-\d+$/.test(tok)) {
|
|
38
|
+
if (row.launcher?.negativeIntegerIsOwnArgument === true && /^-\d+$/.test(tok)) {
|
|
68
39
|
i += 1;
|
|
69
40
|
continue;
|
|
70
41
|
}
|
|
@@ -76,19 +47,6 @@ function pastLauncherOptions(launcher, folded, i) {
|
|
|
76
47
|
i += step;
|
|
77
48
|
}
|
|
78
49
|
}
|
|
79
|
-
function launcherOptionStep(row, tok) {
|
|
80
|
-
const exact = row.optionArity.get(tok);
|
|
81
|
-
if (exact !== undefined)
|
|
82
|
-
return exact === 1 ? 2 : 1;
|
|
83
|
-
const eq = tok.startsWith("--") ? tok.indexOf("=") : -1;
|
|
84
|
-
if (eq > 2 && row.optionArity.get(tok.slice(0, eq)) === 1)
|
|
85
|
-
return 1;
|
|
86
|
-
if (/^-[A-Za-z]./.test(tok) && row.optionArity.get(tok.slice(0, 2)) === 1)
|
|
87
|
-
return 1;
|
|
88
|
-
if (/^-[A-Za-z]+$/.test(tok) && [...tok.slice(1)].every((ch) => row.optionArity.get(`-${ch}`) === 0))
|
|
89
|
-
return 1;
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
50
|
export const PROGRAM_POSITION_REFUSAL_KINDS = Object.freeze(["option_at_program_position", "control_syntax_at_program_position"]);
|
|
93
51
|
export function readProgramPosition(words) {
|
|
94
52
|
const folded = words.map((w) => w.folded);
|
|
@@ -116,20 +74,24 @@ export function readProgramPosition(words) {
|
|
|
116
74
|
const owner = controlWords.length > 0 ? folded[controlWords[controlWords.length - 1]] : head;
|
|
117
75
|
return none(written, assignments, { kind: "option_at_program_position", owner, option: head });
|
|
118
76
|
}
|
|
119
|
-
if (!
|
|
77
|
+
if (!LAUNCHER_NAMES.has(basename)) {
|
|
120
78
|
return { controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: { index: i, name: head, basename }, refusal: undefined };
|
|
121
79
|
}
|
|
122
80
|
const launchers = [{ index: i, name: basename }];
|
|
123
81
|
const owner = head;
|
|
124
|
-
const shellRefusal = () => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: true });
|
|
125
|
-
let
|
|
126
|
-
let
|
|
82
|
+
const shellRefusal = (handsText) => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: true, ...(handsText !== undefined ? { handsText } : {}) });
|
|
83
|
+
let lastRow = launcherRowOf(basename)?.launcher;
|
|
84
|
+
let owedOperands = lastRow?.requiredOperands ?? 0;
|
|
127
85
|
const chain0 = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin: false, runsDefaultProgram: false, program, refusal });
|
|
128
86
|
if (lastRow?.shellText === true)
|
|
129
|
-
return chain0(undefined, shellRefusal());
|
|
130
|
-
let argvFromStdin =
|
|
87
|
+
return chain0(undefined, shellRefusal(true));
|
|
88
|
+
let argvFromStdin = feedsArgvFromStdin(basename);
|
|
131
89
|
let lastReentry = argvFromStdin ? i : -1;
|
|
132
|
-
|
|
90
|
+
let reentryDefault = argvFromStdin ? streamDefaultProgram(basename) : undefined;
|
|
91
|
+
const chain = (program, refusal) => {
|
|
92
|
+
const runsDefaultProgram = lastReentry === folded.length - 1;
|
|
93
|
+
return { controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram, ...(runsDefaultProgram && reentryDefault !== undefined ? { defaultProgram: reentryDefault } : {}), program, refusal };
|
|
94
|
+
};
|
|
133
95
|
for (let j = pastLauncherOptions(basename, folded, i + 1); j < folded.length; j++) {
|
|
134
96
|
const t = folded[j];
|
|
135
97
|
if (owedOperands > 0 && !t.startsWith("-")) {
|
|
@@ -139,50 +101,52 @@ export function readProgramPosition(words) {
|
|
|
139
101
|
if (lastRow?.assignmentOperands === true && !t.startsWith("-") && t.includes("="))
|
|
140
102
|
continue;
|
|
141
103
|
const wordBasename = programBasename(t);
|
|
142
|
-
if (
|
|
104
|
+
if (feedsArgvFromStdin(wordBasename)) {
|
|
143
105
|
argvFromStdin = true;
|
|
144
106
|
lastReentry = j;
|
|
107
|
+
reentryDefault = streamDefaultProgram(wordBasename);
|
|
145
108
|
lastRow = undefined;
|
|
146
109
|
owedOperands = 0;
|
|
147
110
|
continue;
|
|
148
111
|
}
|
|
149
|
-
if (
|
|
112
|
+
if (LAUNCHER_NAMES.has(wordBasename)) {
|
|
150
113
|
launchers.push({ index: j, name: wordBasename });
|
|
151
|
-
lastRow =
|
|
114
|
+
lastRow = launcherRowOf(wordBasename)?.launcher;
|
|
152
115
|
if (lastRow?.shellText === true)
|
|
153
|
-
return chain(undefined, shellRefusal());
|
|
116
|
+
return chain(undefined, shellRefusal(true));
|
|
154
117
|
owedOperands = lastRow?.requiredOperands ?? 0;
|
|
155
118
|
j = pastLauncherOptions(wordBasename, folded, j + 1) - 1;
|
|
156
119
|
continue;
|
|
157
120
|
}
|
|
158
121
|
if (t.startsWith("-"))
|
|
159
122
|
return chain(undefined, { kind: "option_at_program_position", owner, option: t });
|
|
160
|
-
if (SHELL_CONTROL_PREFIXES.has(wordBasename) ||
|
|
123
|
+
if (SHELL_CONTROL_PREFIXES.has(wordBasename) || BLOCK_WORD_NAMES.has(wordBasename)) {
|
|
161
124
|
return chain(undefined, { kind: "control_syntax_at_program_position", owner, word: wordBasename });
|
|
162
125
|
}
|
|
163
126
|
return chain({ index: j, name: t, basename: wordBasename });
|
|
164
127
|
}
|
|
165
|
-
|
|
128
|
+
const last = launchers[launchers.length - 1];
|
|
129
|
+
if (lastRow?.whenNoProgram === "shell" || selectedOptions(launcherRowOf(last.name), folded.slice(last.index + 1)).some((o) => o.mode === "interactive"))
|
|
166
130
|
return chain(undefined, shellRefusal());
|
|
167
131
|
return chain(undefined);
|
|
168
132
|
}
|
|
169
|
-
export function argvReentryUnjudged(pos,
|
|
133
|
+
export function argvReentryUnjudged(pos, acceptsTarget) {
|
|
170
134
|
if (!pos.argvFromStdin)
|
|
171
135
|
return false;
|
|
172
|
-
const runs = pos.program?.basename ??
|
|
173
|
-
return runs === undefined || !
|
|
136
|
+
const runs = pos.program?.basename ?? pos.defaultProgram;
|
|
137
|
+
return runs === undefined || !acceptsTarget(runs);
|
|
174
138
|
}
|
|
175
139
|
export const SHELL_SCAN_DECLINES = Object.freeze(["unreadable_program_position", "stream_fed_operands"]);
|
|
176
|
-
export function programPositionRunsShell(pos,
|
|
140
|
+
export function programPositionRunsShell(pos, runsShell) {
|
|
177
141
|
if (pos.refusal !== undefined)
|
|
178
142
|
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;
|
|
179
|
-
return pos.program !== undefined &&
|
|
143
|
+
return pos.program !== undefined && runsShell(pos.program.basename);
|
|
180
144
|
}
|
|
181
|
-
export function shellScanDeclineOf(pos,
|
|
145
|
+
export function shellScanDeclineOf(pos, acceptsStreamTarget) {
|
|
182
146
|
if (pos.refusal !== undefined) {
|
|
183
147
|
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true ? undefined : "unreadable_program_position";
|
|
184
148
|
}
|
|
185
|
-
return argvReentryUnjudged(pos,
|
|
149
|
+
return argvReentryUnjudged(pos, acceptsStreamTarget) ? "stream_fed_operands" : undefined;
|
|
186
150
|
}
|
|
187
151
|
export function programPositionRefusalSentence(refusal) {
|
|
188
152
|
switch (refusal.kind) {
|