@sema-agent/core 7.46.0 → 7.48.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 +106 -0
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +4 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +23 -6
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +7 -4
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +12 -6
- package/dist/core/permission-rule-model.js +10 -2
- package/dist/core/permission-rules.d.ts +4 -5
- package/dist/core/permission-rules.js +28 -15
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +5 -5
- package/dist/core/runner/prepare-policy-chain.js +2 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +8 -8
- package/dist/core/shell-wrapper-table.js +3 -3
- package/dist/core/tool-catalog-entries.js +3 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +10 -9
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/write-protect.d.ts +10 -3
- package/dist/core/write-protect.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.d.ts +15 -0
- package/dist/shell/lex.js +86 -15
- package/dist/tools/fs/bash-program-position.d.ts +16 -6
- package/dist/tools/fs/bash-program-position.js +18 -12
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +84 -12
- package/dist/tools/fs/program-read-model.js +105 -34
- package/dist/tools/fs/program-read-model.rows.js +26 -7
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +24 -5
- package/dist/tools/fs/shell-read-argv.d.ts +6 -8
- package/dist/tools/fs/shell-read-argv.js +5 -5
- package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
- package/dist/tools/fs/shell-read-bindings.js +190 -75
- package/dist/tools/fs/shell-read-establish.js +6 -7
- package/dist/tools/fs/shell-read-plan.d.ts +3 -15
- package/dist/tools/fs/shell-read-plan.js +126 -129
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +68 -8
- package/dist/tools/fs/shell-read-word.js +106 -12
- package/dist/tools/fs/shell-read-write-targets.js +4 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
- package/dist/tools/fs/shell-reentry-text.js +122 -4
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -3
- package/wire-changes/core@7.47.0.json +412 -0
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTextOf, literalTildeSpelling, } from "../../shell/lex.js";
|
|
2
|
-
import { readOfText, readsOf, rewritten, spelledWithHome, isArithmeticBody, unreadableSentenceOf } from "./shell-read-word.js";
|
|
2
|
+
import { programTextOf, readOfText, readsOf, rewritten, segmentReadingsOf, settledFieldsOf, spelledWithHome, isArithmeticBody, unreadableSentenceOf } from "./shell-read-word.js";
|
|
3
3
|
import { assignmentArithmetic, bare, listedVerbCandidates, pathSubcommandOf, programTextSentence, pushAssignment, roleReads, subscriptOnly, unlistedWords } from "./shell-read-argv.js";
|
|
4
|
-
import { ARITHMETIC_TEST_OPERATORS, BindingThread, arithmeticSubstitutions, isArithmeticCommand, bindingEffectOf, parameterBodySubstitutions, projectSegment, valueOf, } from "./shell-read-bindings.js";
|
|
5
|
-
import { programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
|
|
4
|
+
import { ARITHMETIC_TEST_OPERATORS, BindingThread, arithmeticSubstitutions, isArithmeticCommand, bindingEffectOf, loopListHeadAt, parameterBodySubstitutions, projectSegment, valueOf, } from "./shell-read-bindings.js";
|
|
5
|
+
import { isShellSyntaxWord, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
|
|
6
6
|
import { POLL_LOOP_MAX_BEATS, attachedOptionPayloads, resolveOperandLexically, isPathShapedToken, programTextSlotFiles, } from "./bash-readonly-classifier.js";
|
|
7
|
-
import { BLOCK_WORD_NAMES, commandLineOptionSpellings, declaredAttributes, isDeclarationBuiltin, isInertBuiltin, launcherOptionPathRole, launcherOptionStep, readOptionRole, programLanguageOf, programTextOpensOf, selectedOptions, movesBaseUnfollowed, opensNoOperand, programDelivery, programRow, readArgRoles, runsShellText, } from "./program-read-model.js";
|
|
7
|
+
import { BLOCK_WORD_NAMES, commandLineOf, commandLineOptionSpellings, declaredAttributes, isDeclarationBuiltin, isInertBuiltin, launcherOptionPathRole, launcherOptionStep, readOptionRole, programLanguageOf, programTextOpensOf, selectedOptions, movesBaseUnfollowed, opensNoOperand, programDelivery, programRow, readArgRoles, runsShellText, } from "./program-read-model.js";
|
|
8
8
|
import { isAbsolutePathForm } from "./safety.js";
|
|
9
|
-
import { codeBodies, pipedTextOf, reentryText, scriptFileAt, stdinTextsOf } from "./shell-reentry-text.js";
|
|
9
|
+
import { codeBodies, launchedInput, pipedTextOf, reentryText, scriptFileAt, stdinTextsOf } from "./shell-reentry-text.js";
|
|
10
|
+
import { plannedOnce, rebasePlan, startedAt, stdinFeeds } from "./shell-read-start.js";
|
|
10
11
|
export const READ_CANDIDATE_PROVENANCES = Object.freeze([
|
|
11
12
|
"operand",
|
|
12
13
|
"option_value",
|
|
@@ -22,6 +23,7 @@ export const READ_CANDIDATE_PROVENANCES = Object.freeze([
|
|
|
22
23
|
"object_path",
|
|
23
24
|
]);
|
|
24
25
|
const NESTING_MAX = 4;
|
|
26
|
+
const LOOP_END = new Set(["done"]), COPROC = new Set(["coproc"]);
|
|
25
27
|
const BASE_CANDIDATE_MAX = 256;
|
|
26
28
|
export function planShellReads(command, decl, depth = 0, start) {
|
|
27
29
|
const shape = { grouped: false, backgrounded: false, strayRedirection: false };
|
|
@@ -53,19 +55,19 @@ export function planShellReads(command, decl, depth = 0, start) {
|
|
|
53
55
|
}
|
|
54
56
|
const names = new BindingThread(lexed.segments, named, initial, boundAnywhere, loopEndOf, start?.homeTouched === true, start?.inherited);
|
|
55
57
|
const laterStart = { initial: (name) => (name === "HOME" && !boundAnywhere("HOME") ? declaredHome : "unknown"), boundAnywhere, homeTouched: boundAnywhere("HOME"), inherited: names.inheritedAt() };
|
|
56
|
-
const nestedPlanner = (table) => (text, kind) => planShellReads(text, decl, depth + 1, kind === "substitution" ? { initial: (name) => table.stateOf(name), boundAnywhere, homeTouched: table.homeTouched, inherited: table.inherited } : laterStart);
|
|
58
|
+
const nestedPlanner = (table) => (text, kind, at) => planShellReads(text, decl, depth + 1, kind === "substitution" ? { initial: (name) => table.stateOf(name), boundAnywhere, homeTouched: table.homeTouched, inherited: table.inherited } : startedAt(laterStart, at));
|
|
57
59
|
const empty = lexed.segments.map((segment, si) => segmentIsEmpty(segment, projected[si]));
|
|
58
60
|
const facts = projected.map(() => undefined);
|
|
59
61
|
const tables = projected.map(() => undefined);
|
|
62
|
+
const fedAt = stdinFeeds(lexed.segments, start?.stdin ?? [], (si) => (facts[si - 1] === undefined ? [projected[si - 1].argv] : (facts[si - 1].readings ?? [facts[si - 1]]).map((f) => f.argv)).map((argv) => pipedTextOf(lexed.segments[si - 1], argv)));
|
|
60
63
|
const factsAt = (si, table) => {
|
|
61
64
|
tables[si] ??= table;
|
|
62
|
-
|
|
63
|
-
return (facts[si] ??= segmentFacts(lexed.segments[si], projected[si], decl, table, nestedPlanner(table), piped));
|
|
65
|
+
return (facts[si] ??= segmentFacts(lexed.segments[si], projected[si], decl, table, nestedPlanner(table), fedAt(si)));
|
|
64
66
|
};
|
|
65
67
|
const basesOf = projected.map(() => new Set());
|
|
66
68
|
const unmodelled = projected.map(() => false);
|
|
67
|
-
let candidates =
|
|
68
|
-
let baseLost =
|
|
69
|
+
let candidates = (start?.bases !== undefined && start.bases !== "unmodelled" ? start.bases : [decl.cwd]).map((base) => ({ base, skippedCd: -1 }));
|
|
70
|
+
let baseLost = start?.bases === "unmodelled";
|
|
69
71
|
let floorBases;
|
|
70
72
|
const loseBase = (reachable, floorTarget) => {
|
|
71
73
|
baseLost = true;
|
|
@@ -182,15 +184,15 @@ export function planShellReads(command, decl, depth = 0, start) {
|
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
const planned = facts.map((f) => f);
|
|
185
|
-
const inert = depth === 0 && unreadable === undefined && planned.every(segmentOpensNothing);
|
|
186
|
-
const settled = inert ? planned.map((f) => ({ ...f, reads: f.reads.filter((c) => c.provenance !== "env_assignment") })) : planned;
|
|
187
|
+
const inert = depth === 0 && unreadable === undefined && planned.every((f) => (f.readings ?? [f]).every(segmentOpensNothing));
|
|
188
|
+
const settled = inert ? planned.map((f) => ({ ...f, reads: f.reads.filter((c) => c.provenance !== "env_assignment"), ...(f.readings !== undefined ? { readings: f.readings.map((g) => ({ ...g, reads: g.reads.filter((c) => c.provenance !== "env_assignment") })) } : {}) })) : planned;
|
|
187
189
|
shape.grouped = projected.some((p) => p.grouped);
|
|
188
|
-
shape.backgrounded = lexed.segments.some((s) => s.connector === "&") || settled.some((f) => f.backgrounds === true);
|
|
190
|
+
shape.backgrounded = lexed.segments.some((s) => s.connector === "&") || settled.some((f) => (f.readings ?? [f]).some((g) => g.backgrounds === true));
|
|
189
191
|
shape.strayRedirection = projected.some((p) => p.argv.length === 0 && p.redirects.length > 0);
|
|
190
192
|
const segments = [];
|
|
191
193
|
for (let si = 0; si < settled.length; si++) {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
+
const threaded = settled[si];
|
|
195
|
+
if (threaded.empty)
|
|
194
196
|
continue;
|
|
195
197
|
const bases = unmodelled[si] ? "unmodelled" : [...basesOf[si]];
|
|
196
198
|
const floor = unmodelled[si] && floorBases !== undefined && floorBases.length > 0 ? floorBases : undefined;
|
|
@@ -199,23 +201,26 @@ export function planShellReads(command, decl, depth = 0, start) {
|
|
|
199
201
|
const here = c.provenance === "object_path" && bases !== "unmodelled" ? [...new Set(bases.flatMap(ancestorsOf))] : bases;
|
|
200
202
|
return { ...c, bases: relative ? here : [], ...(relative && floor !== undefined ? { floorBases: floor } : {}) };
|
|
201
203
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
204
|
+
const move = threaded.baseMove;
|
|
205
|
+
for (const f of threaded.readings ?? [threaded]) {
|
|
206
|
+
segments.push({
|
|
207
|
+
text: f.text,
|
|
208
|
+
connector: f.connector,
|
|
209
|
+
argv: f.argv,
|
|
210
|
+
launcherChain: f.launcherChain,
|
|
211
|
+
...(f.verb !== undefined ? { verb: f.verb } : {}),
|
|
212
|
+
...(f.reentry !== undefined ? { reentry: f.reentry } : {}),
|
|
213
|
+
...(f.unreadProgramText !== undefined ? { unreadProgramText: f.unreadProgramText } : {}),
|
|
214
|
+
...(f.stdin !== undefined ? { stdin: f.stdin } : {}),
|
|
215
|
+
declines: f.declines,
|
|
216
|
+
reads: dedupe(f.reads).map(withBases),
|
|
217
|
+
redirects: f.redirects.map((r) => (r.target === undefined ? { op: r.op, direction: r.direction } : { op: r.op, direction: r.direction, target: withBases(r.target) })),
|
|
218
|
+
...(move !== undefined ? { baseMove: { target: typeof move.target === "string" ? move.target : withBases(move.target) } } : {}),
|
|
219
|
+
bases,
|
|
220
|
+
nested: f.nested.map((n) => (f.elsewhere?.has(n) === true ? n : rebasePlan(n, decl.cwd, bases))),
|
|
221
|
+
reach: reaches[si] ?? { andJoined: false, orBroken: false, moveProvable: false },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
219
224
|
}
|
|
220
225
|
return { segments, shape, ...(unreadable !== undefined ? { unreadable } : {}) };
|
|
221
226
|
}
|
|
@@ -232,9 +237,9 @@ function loopShapeOf(p) {
|
|
|
232
237
|
const head = p.argv[0];
|
|
233
238
|
if (head === undefined)
|
|
234
239
|
return undefined;
|
|
235
|
-
if (
|
|
240
|
+
if (loopListHeadAt(p.argv) !== undefined)
|
|
236
241
|
return "head";
|
|
237
|
-
if (head
|
|
242
|
+
if (isShellSyntaxWord(head, LOOP_END))
|
|
238
243
|
return "end";
|
|
239
244
|
const pos = readProgramPosition(p.argv);
|
|
240
245
|
return pos.controlWords.some((i) => p.argv[i].folded === "while" || p.argv[i].folded === "until") ? "head" : undefined;
|
|
@@ -252,8 +257,26 @@ function segmentOpensNothing(f) {
|
|
|
252
257
|
const inertProgram = program === undefined || (program.name === program.basename && isInertBuiltin(program.basename) && !(isDeclarationBuiltin(program.basename) && f.argv.slice(at + 1).some((w) => /^[-+]/.test(w.folded))));
|
|
253
258
|
return inertProgram && f.argv.every((w) => !/[[\]$`]/.test(w.raw)) && f.nested.length === 0 && f.reentry === undefined && f.declines.length === 0 && f.launcherChain.length === 0 && f.redirects.every((r) => r.direction !== "in") && f.reads.every((c) => c.provenance === "env_assignment");
|
|
254
259
|
}
|
|
255
|
-
function segmentFacts(segment, p, decl, table, nestedPlan,
|
|
256
|
-
const
|
|
260
|
+
function segmentFacts(segment, p, decl, table, nestedPlan, fed) {
|
|
261
|
+
const targets = p.redirects.flatMap((r) => (r.target === undefined ? [] : [r.target]));
|
|
262
|
+
const readings = segmentReadingsOf(p.argv, targets, table, decl.homeDir);
|
|
263
|
+
if (readings.length === 1)
|
|
264
|
+
return readingFacts(segment, { ...p, argv: readings[0].argv }, decl, readings[0], nestedPlan, fed);
|
|
265
|
+
const once = plannedOnce(nestedPlan);
|
|
266
|
+
const each = readings.map((reading) => readingFacts(segment, { ...p, argv: reading.argv }, decl, reading, once, fed));
|
|
267
|
+
const first = each[0];
|
|
268
|
+
const move = each.find((f) => f.baseMove !== undefined)?.baseMove;
|
|
269
|
+
return {
|
|
270
|
+
...first,
|
|
271
|
+
moveProvable: false,
|
|
272
|
+
...(move !== undefined ? { baseMove: { target: typeof move.target === "string" ? move.target : { ...move.target, expands: "unresolvable" } } } : {}),
|
|
273
|
+
readings: each,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function readingFacts(segment, p, decl, reading, nestedPlan, fed) {
|
|
277
|
+
const table = reading.table;
|
|
278
|
+
const planner = (text, kind, at) => nestedPlan(text, kind, kind === "later" ? { ...at, floors: reading.floors } : at);
|
|
279
|
+
const facts = segmentFactsRead(segment, p, decl, table, planner, fed, reading.reads);
|
|
257
280
|
const unresolvedRaw = new Set();
|
|
258
281
|
for (const t of segment.tokens) {
|
|
259
282
|
const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
|
|
@@ -269,20 +292,21 @@ function segmentFacts(segment, p, decl, table, nestedPlan, piped) {
|
|
|
269
292
|
};
|
|
270
293
|
const unsettle = (c) => floor((unresolvedRaw.has(c.raw) || (homeWithheld && c.spelled.startsWith("~"))) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
|
|
271
294
|
const each = (c) => {
|
|
272
|
-
const {
|
|
273
|
-
const shadows = (floors ?? []).map((v) => ({ spelled: v.spelled, raw: one.raw, provenance: one.provenance, expands: v.glob === true ? "glob" : false, denyOnly: true }));
|
|
274
|
-
return [
|
|
295
|
+
const { floors, ...one } = c;
|
|
296
|
+
const shadows = (floors ?? []).map((v) => ({ spelled: v.spelled, ...(v.pattern !== undefined ? { pattern: v.pattern } : {}), raw: one.raw, provenance: one.provenance, expands: v.glob === true ? "glob" : false, denyOnly: true }));
|
|
297
|
+
return [unsettle(one), ...shadows];
|
|
275
298
|
};
|
|
276
299
|
const move = facts.baseMove;
|
|
277
300
|
return {
|
|
278
301
|
...facts,
|
|
279
302
|
reads: facts.reads.flatMap(each),
|
|
280
303
|
redirects: facts.redirects.map((r) => (r.target === undefined ? r : { ...r, target: unsettle(r.target) })),
|
|
281
|
-
...(move !== undefined && typeof move.target !== "string" ? { baseMove: { target: unsettle(move.target
|
|
304
|
+
...(move !== undefined && typeof move.target !== "string" ? { baseMove: { target: unsettle(move.target) } } : {}),
|
|
282
305
|
};
|
|
283
306
|
}
|
|
284
|
-
function segmentFactsRead(segment, p, decl, table, nestedPlan,
|
|
307
|
+
function segmentFactsRead(segment, p, decl, table, nestedPlan, fed, toks) {
|
|
285
308
|
const nested = [];
|
|
309
|
+
const elsewhere = new Set();
|
|
286
310
|
const ctx = { table, reentered: [], programTextSeen: false };
|
|
287
311
|
const arithmetic = segment.tokens.flatMap((t) => (t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined)?.pieces ?? []).flatMap((q) => (q.expansion?.kind === "arithmetic" && q.expansion.body !== undefined && isArithmeticBody(q.expansion.body) ? [q.expansion.body] : []));
|
|
288
312
|
const count = (list, body) => list.filter((b) => b === body).length;
|
|
@@ -339,7 +363,6 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
339
363
|
redirects,
|
|
340
364
|
nested,
|
|
341
365
|
};
|
|
342
|
-
const toks = readsOf(p.argv, table, decl.homeDir);
|
|
343
366
|
const done = (f) => {
|
|
344
367
|
const reentered = [...new Set(ctx.reentered)];
|
|
345
368
|
for (const body of reentered)
|
|
@@ -349,23 +372,24 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
349
372
|
...f,
|
|
350
373
|
...(reentered.length > 0 ? { reentry: "shell" } : {}),
|
|
351
374
|
...(f.unreadProgramText === undefined && ctx.programText !== undefined ? { unreadProgramText: ctx.programText } : {}),
|
|
375
|
+
...(elsewhere.size > 0 ? { elsewhere } : {}),
|
|
352
376
|
};
|
|
353
377
|
};
|
|
354
378
|
const head = toks[0]?.folded;
|
|
355
|
-
if (
|
|
379
|
+
if (isShellSyntaxWord(p.argv[0], COPROC) && toks.length > 1) {
|
|
356
380
|
const tail = (from) => ({ ...p, argv: p.argv.slice(from) });
|
|
357
|
-
const first = segmentFacts(segment, tail(1), decl, table, nestedPlan,
|
|
381
|
+
const first = segmentFacts(segment, tail(1), decl, table, nestedPlan, fed);
|
|
358
382
|
if (toks.length < 3)
|
|
359
383
|
return { ...first, backgrounds: true };
|
|
360
|
-
const second = segmentFacts(segment, tail(2), decl, table, nestedPlan,
|
|
384
|
+
const second = segmentFacts(segment, tail(2), decl, table, nestedPlan, fed);
|
|
361
385
|
const seen = new Set(first.nested.map((n) => JSON.stringify(n.segments.map((sg) => sg.text))));
|
|
362
386
|
const extraNested = second.nested.filter((n) => !seen.has(JSON.stringify(n.segments.map((sg) => sg.text))));
|
|
363
387
|
return { ...first, ...(second.reentry === "shell" ? { reentry: second.reentry } : {}), reads: [...first.reads, ...second.reads], nested: [...first.nested, ...extraNested], backgrounds: true };
|
|
364
388
|
}
|
|
365
389
|
const loop = loopShapeOf(p);
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
390
|
+
if (loopListHeadAt(p.argv) !== undefined)
|
|
391
|
+
return done({ empty: false, launcherChain: [], declines: [], reads, moveProvable: false, loopHead: { beats: literalBeats(toks) } });
|
|
392
|
+
if (head !== undefined && isShellSyntaxWord(p.argv[0], BLOCK_WORD_NAMES)) {
|
|
369
393
|
if (head === "[[") {
|
|
370
394
|
toks.forEach((r, i) => {
|
|
371
395
|
if (ARITHMETIC_TEST_OPERATORS.has(toks[i - 1]?.folded ?? "") || ARITHMETIC_TEST_OPERATORS.has(toks[i + 1]?.folded ?? "") || toks[i - 1]?.folded === "-v")
|
|
@@ -413,7 +437,7 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
413
437
|
if (!w.startsWith("-") || w === "-")
|
|
414
438
|
break;
|
|
415
439
|
const step = launcherOptionStep(row, w);
|
|
416
|
-
if (step === undefined)
|
|
440
|
+
if (step === undefined || (step === 2 && j + 1 >= chainEnd))
|
|
417
441
|
break;
|
|
418
442
|
if (step === 2) {
|
|
419
443
|
const role = launcherOptionPathRole(row, w);
|
|
@@ -460,10 +484,39 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
460
484
|
let reentry;
|
|
461
485
|
if (pos.argvFromStdin)
|
|
462
486
|
reentry = "argv";
|
|
487
|
+
const reenterShell = (verb, argvs) => {
|
|
488
|
+
const stdin = stdinTextsOf(segment, fed);
|
|
489
|
+
const scripts = new Map();
|
|
490
|
+
const script = (text, at) => { if (text !== undefined)
|
|
491
|
+
scripts.set(JSON.stringify([text, at]), { ...at, text }); };
|
|
492
|
+
for (const { shell, texts: t, args: a, as } of argvs) {
|
|
493
|
+
const user = as !== undefined ? { as } : {};
|
|
494
|
+
const argvText = [reentryText(shell, t), reentryText(shell, a)];
|
|
495
|
+
for (const text of argvText)
|
|
496
|
+
script(text, { ...user, ...(stdin.length > 0 ? { stdin } : {}) });
|
|
497
|
+
if (argvText.every((text) => text === undefined))
|
|
498
|
+
for (const s of stdin)
|
|
499
|
+
if (s.literal !== undefined)
|
|
500
|
+
script(reentryText(shell, t, s.literal), user);
|
|
501
|
+
}
|
|
502
|
+
for (const { text, ...at } of scripts.values()) {
|
|
503
|
+
const plan = nestedPlan(text, "later", at);
|
|
504
|
+
nested.push(plan);
|
|
505
|
+
if (at.as?.login === true)
|
|
506
|
+
elsewhere.add(plan);
|
|
507
|
+
}
|
|
508
|
+
for (const { shell, args: a, words } of argvs) {
|
|
509
|
+
const at = shell === "source" || shell === "." ? a.findIndex((w) => !w.startsWith("-") || w === "-") : scriptFileAt(shell, a);
|
|
510
|
+
const file = at === undefined ? undefined : words[at];
|
|
511
|
+
if (file !== undefined)
|
|
512
|
+
reads.push(bare(file, "operand"));
|
|
513
|
+
}
|
|
514
|
+
return done({ empty: false, launcherChain, verb, reentry: "shell", declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), ...(stdin.length > 0 ? { stdin } : {}) });
|
|
515
|
+
};
|
|
463
516
|
if (pos.refusal !== undefined) {
|
|
464
|
-
const
|
|
465
|
-
if (pos.refusal.kind === "option_at_program_position" &&
|
|
466
|
-
const text = commandStringValue(
|
|
517
|
+
const owner = programRow(programBasename(pos.refusal.owner));
|
|
518
|
+
if (pos.refusal.kind === "option_at_program_position" && owner !== undefined && commandLineOptionSpellings(owner).length > 0) {
|
|
519
|
+
const text = commandStringValue(owner, toks, pos.refusal.option);
|
|
467
520
|
if (text !== undefined) {
|
|
468
521
|
nested.push(nestedPlan(text.value, "later"));
|
|
469
522
|
reentry = "shell";
|
|
@@ -472,16 +525,16 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
472
525
|
}
|
|
473
526
|
}
|
|
474
527
|
if (programPositionRunsShell(pos, runsShellText)) {
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return
|
|
528
|
+
const launched = launchedInput(p.argv, pos, { text: (k) => literalTextOf(toks[k].word.pieces) ?? programTextOf(toks[k]), folded: (k) => programTextOf(toks[k]), values: (k) => settledFieldsOf(toks[k]) });
|
|
529
|
+
for (const k of launched.programWords)
|
|
530
|
+
if (isPathShapedToken(toks[k].folded))
|
|
531
|
+
reads.push(bare(toks[k], "program_path"));
|
|
532
|
+
for (const text of launched.lines)
|
|
533
|
+
nested.push(nestedPlan(text, "substitution"));
|
|
534
|
+
const argvs = launched.shells.map((a) => ({ ...a, words: a.words.map((k) => (k === undefined ? undefined : toks[k])) }));
|
|
535
|
+
if (argvs.length === 0)
|
|
536
|
+
return done({ empty: false, launcherChain, reentry: "shell", declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}) });
|
|
537
|
+
return reenterShell({ name: argvs[0].shell, basename: argvs[0].shell, listed: decl.readers.has(argvs[0].shell) }, argvs);
|
|
485
538
|
}
|
|
486
539
|
const from = pos.launchers[0]?.index ?? written;
|
|
487
540
|
unlistedWords(reads, toks, from, launcherIndices, "unlisted_word", ctx);
|
|
@@ -493,31 +546,13 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
493
546
|
return done({ empty: false, launcherChain, ...(reentry !== undefined ? { reentry } : {}), declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}) });
|
|
494
547
|
}
|
|
495
548
|
const argReads = toks.slice(program.index + 1);
|
|
496
|
-
const args = argReads.map(
|
|
497
|
-
const texts = argReads.map((r) => literalTextOf(r.word.pieces) ?? r
|
|
549
|
+
const args = argReads.map(programTextOf);
|
|
550
|
+
const texts = argReads.map((r) => literalTextOf(r.word.pieces) ?? programTextOf(r));
|
|
498
551
|
const programRead = toks[program.index];
|
|
499
552
|
if (rewritten(programRead) || isPathShapedToken(programRead.folded))
|
|
500
553
|
reads.push(bare(programRead, "program_path"));
|
|
501
|
-
if (runsShellText(basename))
|
|
502
|
-
|
|
503
|
-
const stdin = stdinTextsOf(segment, piped);
|
|
504
|
-
const text = reentryText(basename, texts);
|
|
505
|
-
const scripts = [text, reentryText(basename, args), ...stdin.map((s) => (s.literal === undefined ? undefined : reentryText(basename, texts, s.literal)))];
|
|
506
|
-
for (const t of new Set(scripts))
|
|
507
|
-
if (t !== undefined)
|
|
508
|
-
nested.push(nestedPlan(t, "later"));
|
|
509
|
-
if (basename === "source" || basename === ".") {
|
|
510
|
-
const file = argReads.find((a) => !a.folded.startsWith("-") || a.folded === "-");
|
|
511
|
-
if (file !== undefined)
|
|
512
|
-
reads.push(bare(file, "operand"));
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
const file = scriptFileAt(basename, args);
|
|
516
|
-
if (file !== undefined)
|
|
517
|
-
reads.push(bare(argReads[file], "operand"));
|
|
518
|
-
}
|
|
519
|
-
return done({ empty: false, launcherChain, verb: verb, reentry, declines, reads, moveProvable: false, ...(loopHead !== undefined ? { loopHead } : {}), ...(stdin.length > 0 ? { stdin } : {}) });
|
|
520
|
-
}
|
|
554
|
+
if (runsShellText(basename))
|
|
555
|
+
return reenterShell(verb, [{ shell: basename, texts, args, words: argReads }]);
|
|
521
556
|
const row = programRow(basename);
|
|
522
557
|
const delivery = programDelivery(row, args);
|
|
523
558
|
if (delivery !== undefined)
|
|
@@ -600,38 +635,6 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, piped) {
|
|
|
600
635
|
...(loopHead !== undefined ? { loopHead } : {}),
|
|
601
636
|
});
|
|
602
637
|
}
|
|
603
|
-
function rebasePlan(plan, from, to) {
|
|
604
|
-
if (to !== "unmodelled" && to.length === 1 && to[0] === from)
|
|
605
|
-
return plan;
|
|
606
|
-
const fromSep = from.endsWith("/") || from.endsWith("\\") ? from : `${from}/`;
|
|
607
|
-
const moveBase = (b) => {
|
|
608
|
-
if (to === "unmodelled")
|
|
609
|
-
return [];
|
|
610
|
-
if (b === from)
|
|
611
|
-
return [...to];
|
|
612
|
-
if (b.startsWith(fromSep))
|
|
613
|
-
return to.map((t) => `${t.endsWith("/") || t.endsWith("\\") ? t : `${t}/`}${b.slice(fromSep.length)}`);
|
|
614
|
-
return [b];
|
|
615
|
-
};
|
|
616
|
-
const moveCandidate = (c) => {
|
|
617
|
-
if (c.bases === "unmodelled" || c.bases.length === 0)
|
|
618
|
-
return c;
|
|
619
|
-
if (to === "unmodelled")
|
|
620
|
-
return { ...c, bases: "unmodelled" };
|
|
621
|
-
return { ...c, bases: [...new Set(c.bases.flatMap(moveBase))] };
|
|
622
|
-
};
|
|
623
|
-
return {
|
|
624
|
-
...plan,
|
|
625
|
-
segments: plan.segments.map((s) => ({
|
|
626
|
-
...s,
|
|
627
|
-
reads: s.reads.map(moveCandidate),
|
|
628
|
-
redirects: s.redirects.map((r) => (r.target === undefined ? r : { ...r, target: moveCandidate(r.target) })),
|
|
629
|
-
...(s.baseMove !== undefined ? { baseMove: { target: typeof s.baseMove.target === "string" ? s.baseMove.target : moveCandidate(s.baseMove.target) } } : {}),
|
|
630
|
-
bases: s.bases === "unmodelled" || to === "unmodelled" ? "unmodelled" : [...new Set(s.bases.flatMap(moveBase))],
|
|
631
|
-
nested: s.nested.map((n) => rebasePlan(n, from, to)),
|
|
632
|
-
})),
|
|
633
|
-
};
|
|
634
|
-
}
|
|
635
638
|
function dedupe(reads) {
|
|
636
639
|
const seen = new Set();
|
|
637
640
|
const out = [];
|
|
@@ -659,21 +662,15 @@ function redirectFact(r, table, homeDir) {
|
|
|
659
662
|
return { op: r.op, direction: "in" };
|
|
660
663
|
return { op: r.op, direction: "out", target: bare(readsOf([r.target], table, homeDir)[0], "write_redirect_target") };
|
|
661
664
|
}
|
|
662
|
-
function commandStringValue(
|
|
665
|
+
function commandStringValue(row, toks, option) {
|
|
663
666
|
const at = toks.findIndex((r) => r.folded === option);
|
|
664
|
-
|
|
667
|
+
const line = at < 0 ? undefined : commandLineOf(row, option);
|
|
668
|
+
if (line === undefined)
|
|
665
669
|
return undefined;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
if (spelling.startsWith("--") && option.startsWith(`${spelling}=`))
|
|
672
|
-
return { value: option.slice(spelling.length + 1), next: at + 1 };
|
|
673
|
-
if (!spelling.startsWith("--") && option.startsWith(spelling) && option.length > spelling.length)
|
|
674
|
-
return { value: option.slice(spelling.length), next: at + 1 };
|
|
675
|
-
}
|
|
676
|
-
return undefined;
|
|
670
|
+
if (line !== "next")
|
|
671
|
+
return { value: line.attached, next: at + 1 };
|
|
672
|
+
const value = toks[at + 1] === undefined ? undefined : programTextOf(toks[at + 1]);
|
|
673
|
+
return value === undefined ? undefined : { value, next: at + 2 };
|
|
677
674
|
}
|
|
678
675
|
function literalBeats(toks) {
|
|
679
676
|
const at = toks.findIndex((r) => r.folded === "in");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a text the read plan (`shell-read-plan.ts`) plans starts: a nested text's start — its names, the deny floors the
|
|
3
|
+
* words handing it on carry, the user its shell runs as, its standard input and its working directory — and where each
|
|
4
|
+
* segment's standard input comes from. A nested plan parsed against the declarations' working directory is re-based onto
|
|
5
|
+
* the segment it sits in ({@link rebasePlan}), unless it starts in a working directory of its own.
|
|
6
|
+
*/
|
|
7
|
+
import type { LexedSegment } from "../../shell/lex.js";
|
|
8
|
+
import type { BindingTable, FloorValue, NameReader } from "./shell-read-bindings.js";
|
|
9
|
+
import type { TargetUser } from "./program-read-model.js";
|
|
10
|
+
import type { StdinText } from "./shell-reentry-text.js";
|
|
11
|
+
import type { ReadCandidate, ShellReadPlan } from "./shell-read-plan.js";
|
|
12
|
+
/** Where a nested text starts its names: a substitution body inherits the table of the segment it sits in (a subshell
|
|
13
|
+
* holds every variable); a text the shell runs later or elsewhere (a code body, `eval`'s text, a child shell's `-c`)
|
|
14
|
+
* starts from the text's own start — every name unknown, `HOME` the declared home unless the enclosing text binds it
|
|
15
|
+
* anywhere. `boundAnywhere` answers for the enclosing texts. */
|
|
16
|
+
export interface PlanStart {
|
|
17
|
+
readonly initial: NameReader;
|
|
18
|
+
readonly boundAnywhere: (name: string) => boolean;
|
|
19
|
+
/** `HOME` may already hold something other than the declared home where the nested text starts. */
|
|
20
|
+
readonly homeTouched: boolean;
|
|
21
|
+
/** The attributes, name references and deny floors in force where the nested text starts. */
|
|
22
|
+
readonly inherited: BindingTable["inherited"];
|
|
23
|
+
/** The working directories it starts in, where they are not the enclosing segment's (a login shell starts in its user's
|
|
24
|
+
* home; `"unmodelled"` — a home the text does not settle). */
|
|
25
|
+
readonly bases?: readonly string[] | "unmodelled";
|
|
26
|
+
/** What arrives on its own standard input (the input of the shell running it). */
|
|
27
|
+
readonly stdin?: readonly StdinText[];
|
|
28
|
+
}
|
|
29
|
+
/** What a segment says of the start of a text it hands a shell to run later: the deny floors of the words it holds as
|
|
30
|
+
* written, the user the shell runs as ({@link TargetUser}), and the shell's standard input. */
|
|
31
|
+
export interface NestedStart {
|
|
32
|
+
readonly floors?: ReadonlyMap<string, readonly FloorValue[]>;
|
|
33
|
+
readonly as?: TargetUser;
|
|
34
|
+
readonly stdin?: readonly StdinText[];
|
|
35
|
+
}
|
|
36
|
+
/** Where a text run later starts ({@link NestedStart}): the later start, with the shell's standard input, the floors of the
|
|
37
|
+
* words that hand it on, and — run as another user — that user's `HOME` (the caller's, the target's, or either) and, for
|
|
38
|
+
* a login shell, the target's home as its working directory. */
|
|
39
|
+
export declare function startedAt(later: PlanStart, at: NestedStart | undefined): PlanStart;
|
|
40
|
+
/** A planner that plans each text once per start: a segment read in several readings hands most of its nested texts (a
|
|
41
|
+
* substitution's body, a launched shell's text) on alike in every reading. */
|
|
42
|
+
export declare function plannedOnce<K, P>(plan: (text: string, kind: K, at?: NestedStart) => P): (text: string, kind: K, at?: NestedStart) => P;
|
|
43
|
+
/**
|
|
44
|
+
* What arrives on each segment's standard input: the pipe it is reached through (`piped`, what the segment before it
|
|
45
|
+
* prints); else the input of the group (`{ …; }`, `( … )`) it stands in — a group a pipe feeds hands each member that
|
|
46
|
+
* pipe; else the input the text itself was handed (`start`: a shell's `-c` text reads that shell's standard input).
|
|
47
|
+
*/
|
|
48
|
+
export declare function stdinFeeds(segments: readonly LexedSegment[], start: readonly StdinText[], piped: (si: number) => readonly StdinText[]): (si: number) => readonly StdinText[];
|
|
49
|
+
/**
|
|
50
|
+
* A nested plan (a substitution body, a here-document's substitution, a launcher's command string) was parsed
|
|
51
|
+
* against the declarations' working directory; the segment it sits in may stand elsewhere (`cd sub && cat <<EOF
|
|
52
|
+
* $(cat secret)`), so its relative candidates are re-based onto the ENCLOSING segment's bases — every base the
|
|
53
|
+
* nested text derived from the start is carried over by prefix, an unsettled enclosing base leaves every
|
|
54
|
+
* relative candidate unplaced, and absolute candidates are untouched. Recursive: a nested plan's own nested plans
|
|
55
|
+
* derive from the same start.
|
|
56
|
+
*/
|
|
57
|
+
export declare function rebasePlan(plan: ShellReadPlan, from: string, to: ReadCandidate["bases"]): ShellReadPlan;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const ROOT_HOMES = ["/root", "/var/root"];
|
|
2
|
+
export function startedAt(later, at) {
|
|
3
|
+
if (at === undefined)
|
|
4
|
+
return later;
|
|
5
|
+
const fed = at.stdin !== undefined && at.stdin.length > 0 ? { ...later, stdin: at.stdin } : later;
|
|
6
|
+
const withFloors = at.floors === undefined || at.floors.size === 0 ? fed : { ...fed, inherited: { ...fed.inherited, floors: joinedFloors(fed.inherited.floors, at.floors) } };
|
|
7
|
+
const as = at.as;
|
|
8
|
+
if (as === undefined || as.home === "caller")
|
|
9
|
+
return withFloors;
|
|
10
|
+
const homes = as.name === "root" ? ROOT_HOMES : undefined;
|
|
11
|
+
const target = homes === undefined ? "unknown" : homes.map((spelled) => ({ spelled }));
|
|
12
|
+
const caller = later.initial("HOME");
|
|
13
|
+
const home = as.home === "target" ? target : caller === "unknown" || target === "unknown" ? "unknown" : [...caller, ...target];
|
|
14
|
+
return {
|
|
15
|
+
...withFloors,
|
|
16
|
+
initial: (name) => (name === "HOME" ? home : withFloors.initial(name)),
|
|
17
|
+
homeTouched: true,
|
|
18
|
+
...(as.login ? { bases: homes ?? "unmodelled" } : {}),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function plannedOnce(plan) {
|
|
22
|
+
const planned = new Map();
|
|
23
|
+
return (text, kind, at) => {
|
|
24
|
+
const key = JSON.stringify([kind, text, [...(at?.floors ?? [])], at?.as, at?.stdin]);
|
|
25
|
+
if (!planned.has(key))
|
|
26
|
+
planned.set(key, plan(text, kind, at));
|
|
27
|
+
return planned.get(key);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function joinedFloors(a, b) {
|
|
31
|
+
const out = new Map(a);
|
|
32
|
+
for (const [name, floors] of b)
|
|
33
|
+
out.set(name, [...(out.get(name) ?? []), ...floors]);
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
export function stdinFeeds(segments, start, piped) {
|
|
37
|
+
const opener = [];
|
|
38
|
+
const enclosing = [];
|
|
39
|
+
const open = [];
|
|
40
|
+
segments.forEach((segment, si) => {
|
|
41
|
+
enclosing[si] = open.at(-1);
|
|
42
|
+
let opens = false;
|
|
43
|
+
for (const t of segment.tokens) {
|
|
44
|
+
if ((t.kind === "group" && t.op === "(") || (t.kind === "word" && t.raw === "{")) {
|
|
45
|
+
open.push(si);
|
|
46
|
+
opens = true;
|
|
47
|
+
}
|
|
48
|
+
else if ((t.kind === "group" && t.op === ")") || (t.kind === "word" && t.raw === "}"))
|
|
49
|
+
open.pop();
|
|
50
|
+
}
|
|
51
|
+
opener[si] = opens ? si : enclosing[si];
|
|
52
|
+
});
|
|
53
|
+
const fedAt = (si) => {
|
|
54
|
+
if (si > 0 && segments[si].connector === "|")
|
|
55
|
+
return [...new Map(piped(si).map((s) => [JSON.stringify(s), s])).values()];
|
|
56
|
+
const group = opener[si] !== si ? opener[si] : enclosing[si];
|
|
57
|
+
return group !== undefined ? fedAt(group) : start;
|
|
58
|
+
};
|
|
59
|
+
return fedAt;
|
|
60
|
+
}
|
|
61
|
+
export function rebasePlan(plan, from, to) {
|
|
62
|
+
if (to !== "unmodelled" && to.length === 1 && to[0] === from)
|
|
63
|
+
return plan;
|
|
64
|
+
const fromSep = from.endsWith("/") || from.endsWith("\\") ? from : `${from}/`;
|
|
65
|
+
const moveBase = (b) => {
|
|
66
|
+
if (to === "unmodelled")
|
|
67
|
+
return [];
|
|
68
|
+
if (b === from)
|
|
69
|
+
return [...to];
|
|
70
|
+
if (b.startsWith(fromSep))
|
|
71
|
+
return to.map((t) => `${t.endsWith("/") || t.endsWith("\\") ? t : `${t}/`}${b.slice(fromSep.length)}`);
|
|
72
|
+
return [b];
|
|
73
|
+
};
|
|
74
|
+
const moveCandidate = (c) => {
|
|
75
|
+
if (c.bases === "unmodelled" || c.bases.length === 0)
|
|
76
|
+
return c;
|
|
77
|
+
if (to === "unmodelled")
|
|
78
|
+
return { ...c, bases: "unmodelled" };
|
|
79
|
+
return { ...c, bases: [...new Set(c.bases.flatMap(moveBase))] };
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
...plan,
|
|
83
|
+
segments: plan.segments.map((s) => ({
|
|
84
|
+
...s,
|
|
85
|
+
reads: s.reads.map(moveCandidate),
|
|
86
|
+
redirects: s.redirects.map((r) => (r.target === undefined ? r : { ...r, target: moveCandidate(r.target) })),
|
|
87
|
+
...(s.baseMove !== undefined ? { baseMove: { target: typeof s.baseMove.target === "string" ? s.baseMove.target : moveCandidate(s.baseMove.target) } } : {}),
|
|
88
|
+
bases: s.bases === "unmodelled" || to === "unmodelled" ? "unmodelled" : [...new Set(s.bases.flatMap(moveBase))],
|
|
89
|
+
nested: s.nested.map((n) => rebasePlan(n, from, to)),
|
|
90
|
+
})),
|
|
91
|
+
};
|
|
92
|
+
}
|