@sema-agent/core 7.47.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/core/allow-entry.js +1 -1
  3. package/dist/core/checkpoint-store.d.ts +1 -1
  4. package/dist/core/mcp-server-spec.d.ts +1 -1
  5. package/dist/core/runner/park-commit.d.ts +1 -1
  6. package/dist/core/safety-axis-vocab.d.ts +1 -1
  7. package/dist/core/safety-merge-corpus.d.ts +1 -1
  8. package/dist/core/session-policy-store.d.ts +6 -3
  9. package/dist/core/session-policy-store.js +28 -10
  10. package/dist/core/shell-wrapper-table.d.ts +2 -2
  11. package/dist/core/shell-wrapper-table.js +1 -1
  12. package/dist/core/tool-name-aliases.d.ts +9 -8
  13. package/dist/core/tool-name-aliases.js +2 -0
  14. package/dist/core/tool-policy.js +4 -5
  15. package/dist/orchestration/workflow.js +2 -2
  16. package/dist/prompt-assembly/types.d.ts +1 -1
  17. package/dist/shell/lex.js +30 -4
  18. package/dist/tools/fs/bash-program-position.d.ts +7 -0
  19. package/dist/tools/fs/bash-program-position.js +5 -2
  20. package/dist/tools/fs/index.d.ts +3 -2
  21. package/dist/tools/fs/index.js +2 -2
  22. package/dist/tools/fs/program-read-model.d.ts +60 -5
  23. package/dist/tools/fs/program-read-model.js +100 -36
  24. package/dist/tools/fs/program-read-model.rows.js +13 -5
  25. package/dist/tools/fs/read-deny-home.d.ts +20 -1
  26. package/dist/tools/fs/read-deny-home.js +28 -7
  27. package/dist/tools/fs/read-deny.d.ts +13 -6
  28. package/dist/tools/fs/read-deny.js +12 -3
  29. package/dist/tools/fs/removal-targets.js +12 -3
  30. package/dist/tools/fs/shell-read-argv.d.ts +4 -7
  31. package/dist/tools/fs/shell-read-argv.js +5 -7
  32. package/dist/tools/fs/shell-read-bindings.d.ts +34 -9
  33. package/dist/tools/fs/shell-read-bindings.js +166 -60
  34. package/dist/tools/fs/shell-read-establish.js +3 -4
  35. package/dist/tools/fs/shell-read-plan.d.ts +1 -14
  36. package/dist/tools/fs/shell-read-plan.js +102 -101
  37. package/dist/tools/fs/shell-read-start.d.ts +57 -0
  38. package/dist/tools/fs/shell-read-start.js +92 -0
  39. package/dist/tools/fs/shell-read-word.d.ts +35 -6
  40. package/dist/tools/fs/shell-read-word.js +85 -9
  41. package/dist/tools/fs/shell-reentry-text.d.ts +23 -21
  42. package/dist/tools/fs/shell-reentry-text.js +31 -26
  43. package/package.json +1 -1
  44. package/wire-changes/core@7.48.0.json +252 -0
@@ -1,13 +1,13 @@
1
1
  import { argvOf, lexShell, literalTildeSpelling, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
2
2
  import { expansionInWord, extglobOpenerIn, globInWord, homeExpansionOf } from "./bash-readonly-classifier.js";
3
- import { wordFloorValues, wordValues } from "./shell-read-bindings.js";
4
- export function readsOf(words, table, homeDir) {
3
+ import { floorOf, judgedSpellingOf, namesIn, wordFloorValues, wordValues } from "./shell-read-bindings.js";
4
+ export function readsOf(words, table, homeDir, split = false) {
5
5
  return words.map((word) => {
6
- const refersToName = word.pieces.some((p) => p.expansion?.kind === "parameter");
6
+ const refersToName = word.pieces.some((p) => p.expansion?.kind === "parameter") || namesIn(word).length > 0;
7
7
  const asWritten = { word, raw: word.raw, folded: word.folded, ...(word.judged !== undefined ? { pattern: word.judged } : {}) };
8
8
  if (!refersToName && table?.homeTouched !== true)
9
9
  return { ...asWritten, expands: expandsOf(word) };
10
- const values = wordValues(word, table, false);
10
+ const values = wordValues(word, table, false, undefined, split);
11
11
  if (values === undefined)
12
12
  return { ...asWritten, expands: expandsOf(word) };
13
13
  if (values === "unknown") {
@@ -18,7 +18,7 @@ export function readsOf(words, table, homeDir) {
18
18
  const refersToHome = word.pieces.some((p) => p.expansion?.kind === "tilde" || (p.expansion?.kind === "parameter" && p.expansion.name === "HOME"));
19
19
  const declared = refersToHome && homeDir !== undefined && table?.homeTouched === true ? wordValues(word, table, false, new Map([["HOME", [{ spelled: homeDir }]]])) : undefined;
20
20
  const floor = declared !== undefined && declared !== "unknown" && declared[0] !== undefined ? spellingOf(declared[0]) : undefined;
21
- const pattern = patternOf(values[0]);
21
+ const pattern = judgedSpellingOf(values[0]);
22
22
  return {
23
23
  word,
24
24
  raw: word.raw,
@@ -31,12 +31,88 @@ export function readsOf(words, table, homeDir) {
31
31
  };
32
32
  });
33
33
  }
34
+ const READINGS_MAX = 64;
35
+ export function segmentReadingsOf(argv, targets, table, homeDir) {
36
+ const several = new Map();
37
+ for (const word of [...argv, ...targets]) {
38
+ for (const name of namesReadIn(word, table)) {
39
+ const state = table.stateOf(name);
40
+ if (state !== "unknown" && state.length > 1)
41
+ several.set(name, state);
42
+ }
43
+ }
44
+ let overlays = [new Map()];
45
+ const past = [];
46
+ for (const [name, values] of several) {
47
+ if (past.length > 0 || overlays.length * values.length > READINGS_MAX) {
48
+ past.push(name);
49
+ continue;
50
+ }
51
+ overlays = overlays.flatMap((o) => values.map((v) => new Map(o).set(name, [v])));
52
+ }
53
+ for (const o of overlays)
54
+ for (const name of past)
55
+ o.set(name, "unknown");
56
+ return overlays.map((overlay) => {
57
+ const reading = overlay.size === 0 ? table : overlaidTable(table, overlay);
58
+ const fielded = readsOf(argv, reading, homeDir, true).flatMap((r) => {
59
+ const fields = r.values?.length === 1 ? r.values[0].fields : undefined;
60
+ return fields === undefined ? [r] : fields.map((f) => ({ word: argumentWordOf(f, f) ?? r.word, raw: f, folded: spellingOf({ spelled: f }), expands: false, settled: true, values: [{ spelled: f }] }));
61
+ });
62
+ const floors = new Map();
63
+ for (const r of fielded) {
64
+ if (r.settled === true)
65
+ continue;
66
+ for (const p of r.word.pieces) {
67
+ const name = p.expansion?.kind === "parameter" ? p.expansion.name : undefined;
68
+ if (name === undefined)
69
+ continue;
70
+ const held = reading.stateOf(name);
71
+ const floor = [...(held === "unknown" ? [] : held.map(floorOf)), ...reading.floorOf(name)];
72
+ if (floor.length > 0)
73
+ floors.set(name, floor);
74
+ }
75
+ }
76
+ return { table: reading, reads: fielded, argv: fielded.map((r) => valuedWord(r.word, r)), floors };
77
+ });
78
+ }
79
+ function namesReadIn(word, table) {
80
+ return [...namesIn(word), ...(table.homeTouched && word.pieces.some((p) => p.expansion?.kind === "tilde" && p.expansion.body === "~") ? ["HOME"] : [])];
81
+ }
82
+ function overlaidTable(table, overlay) {
83
+ return {
84
+ stateOf: (name) => overlay.get(name) ?? table.stateOf(name),
85
+ isInteger: (name) => table.isInteger(name),
86
+ ifsTouched: table.ifsTouched,
87
+ homeTouched: table.homeTouched,
88
+ floorOf: (name) => {
89
+ const held = table.stateOf(name);
90
+ return overlay.get(name) !== "unknown" || held === "unknown" ? table.floorOf(name) : [...table.floorOf(name), ...held.map(floorOf)];
91
+ },
92
+ inherited: table.inherited,
93
+ };
94
+ }
95
+ function valuedWord(word, r) {
96
+ const v = r.values?.length === 1 ? r.values[0] : undefined;
97
+ if (word.assignment !== undefined || v === undefined || v.glob === true || v.spelled === word.folded)
98
+ return word;
99
+ return argumentWordOf(v.spelled, r.raw) ?? word;
100
+ }
101
+ function argumentWordOf(text, raw) {
102
+ const quoted = text.replace(/[^A-Za-z0-9_./+@:%=,-]/g, (c) => (c === "'" ? "\\'" : `'${c}'`));
103
+ const lexed = lexShell(`: ${quoted === "" ? "''" : quoted}`);
104
+ const words = "parseFailure" in lexed || lexed.segments.length !== 1 ? undefined : argvOf(lexed.segments[0]);
105
+ return words?.length === 2 ? { ...words[1], raw } : undefined;
106
+ }
34
107
  export const spellingOf = (v) => literalTildeSpelling(v.spelled);
35
- export function patternOf(v) {
36
- if (v.glob !== true)
108
+ export function partPattern(word, part) {
109
+ if (word.pattern === undefined || part === word.folded || !word.folded.endsWith(part))
110
+ return undefined;
111
+ const front = word.folded.slice(0, word.folded.length - part.length);
112
+ if (!word.pattern.startsWith(front))
37
113
  return undefined;
38
- const judged = literalTildeSpelling(v.parts.map((p) => (p.pattern ? p.judged : p.text)).join(""));
39
- return judged === spellingOf(v) ? undefined : judged;
114
+ const judged = word.pattern.slice(front.length);
115
+ return judged === part ? undefined : judged;
40
116
  }
41
117
  export function renamed(c, spelled) {
42
118
  const { pattern: _matched, ...rest } = c;
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { type LexedSegment, type LexedWord } from "../../shell/lex.js";
8
8
  import { type ProgramPosition } from "./bash-program-position.js";
9
+ import { type TargetUser } from "./program-read-model.js";
9
10
  import type { BoundValue } from "./shell-read-bindings.js";
10
11
  /**
11
12
  * The code bodies a builtin keeps among its operands, one per body; empty for every other program. The program read
@@ -52,6 +53,8 @@ export interface LaunchedCommand {
52
53
  readonly spelled: string;
53
54
  };
54
55
  readonly args: readonly LaunchedArg[];
56
+ /** The user the launcher runs it as, where its row says it runs another ({@link targetUserOf}). */
57
+ readonly as?: TargetUser;
55
58
  }
56
59
  /**
57
60
  * What a launcher that runs a program named by no word of its own runs ({@link readProgramPosition}'s `runsShell`: the
@@ -79,11 +82,11 @@ export declare function launchedReadings(argv: readonly LexedWord[], command: La
79
82
  readonly shell?: string;
80
83
  readonly line?: string;
81
84
  };
82
- /** What a launcher runs ({@link launchedCommands}, {@link launchedReadings}), as a reader of the command line reads it:
83
- * each shell's argv in two spellings — `texts` (a word's literal value, else `spell.text`) and `args` (`spell.folded`) —
84
- * with the argv index each position stands for (`words`), one argv per reading of the values its words may take
85
- * (`spell.values`, {@link spellLaunched}); the word naming a shell (`su -s /bin/bash`, `programWords`); and each command
86
- * line a program that is no shell runs (`lines`, once each). */
85
+ /** What a launcher runs ({@link launchedCommands}, {@link launchedReadings}), as a reader of the command line reads it in
86
+ * one reading of its names: each shell's argv in two spellings — `texts` (a word's literal value, else `spell.text`) and
87
+ * `args` (`spell.folded`) — with the argv index each position stands for (`words`), its words' fields read off
88
+ * `spell.values` ({@link spellLaunched}) and the user it runs as (`as`); the word naming a shell (`su -s /bin/bash`,
89
+ * `programWords`); and each command line a program that is no shell runs (`lines`, once each). */
87
90
  export declare function launchedInput(argv: readonly LexedWord[], pos: ProgramPosition, spell: {
88
91
  readonly text: (k: number) => string;
89
92
  readonly folded: (k: number) => string;
@@ -94,6 +97,7 @@ export declare function launchedInput(argv: readonly LexedWord[], pos: ProgramPo
94
97
  readonly texts: readonly string[];
95
98
  readonly args: readonly string[];
96
99
  readonly words: readonly (number | undefined)[];
100
+ readonly as?: TargetUser;
97
101
  }[];
98
102
  readonly programWords: readonly number[];
99
103
  readonly lines: readonly string[];
@@ -107,19 +111,16 @@ export interface LaunchedSpelling {
107
111
  readonly values?: (at: number) => readonly BoundValue[] | undefined;
108
112
  }
109
113
  /**
110
- * The argv a launched shell is handed ({@link launchedCommands}), spelled as `spell` spells the command line's words — one
111
- * argv per reading of the values its words may take. A word an escaping launcher joins (`sudo -s CMD…`) is handed as the
112
- * launcher hands it: each FIELD the command line's shell gives the launcher, escaped ({@link sudoEscaped}) — the field a
113
- * literal word spells, or each field the shell established for a word it rewrites first (`X='#'; sudo -s rm -r "$X" d`
114
- * hands `rm -r \# d`: the value is an operand, never a comment), a pattern's literal characters escaped and its pattern
115
- * characters bare ({@link launchedSpellingOf}). A word whose fields the reader does not know is handed AS WRITTEN: the
116
- * launched shell re-reads the expansion with the command line's quoting, so the word stays one argument (an unknown there)
117
- * and its neighbours stay theirs (`"#$Y" d` is never `#$Y d`). A reading gives each distinct word spelling one value — the
118
- * same word spelled twice expands alike — and words with several values are read in every combination of them (a value in
119
- * the program's position meets each operand); past {@link LAUNCHED_READINGS_MAX}
120
- * combinations reading `i` hands each word its `i`-th value (its last where it holds fewer) — every value of every word is
121
- * still read, the same word spelled twice takes one value per reading as the shell expands it, and the readings stay as
122
- * many as one word's values: their cost is linear in the command, whatever its length.
114
+ * The argv a launched shell is handed ({@link launchedCommands}), spelled as `spell` spells the command line's words in ONE
115
+ * reading of their names (the plan reads a segment once per combination of its names' values, `segmentReadingsOf` in
116
+ * shell-read-word.ts — a word's value here is its value in that reading). A word an escaping launcher joins (`sudo -s
117
+ * CMD…`) is handed as the launcher hands it: the FIELD the command line's shell gives the launcher, escaped
118
+ * ({@link sudoEscaped}) — the field a literal word spells, or the field the shell established for a word it rewrites first
119
+ * (`X='#'; sudo -s rm -r "$X" d` hands `rm -r \# d`: the value is an operand, never a comment), a pattern's literal
120
+ * characters escaped and its pattern characters bare ({@link launchedSpellingOf}). A word whose field the reader does not
121
+ * know is handed AS WRITTEN: the launched shell re-reads the expansion with the command line's quoting, so the word stays
122
+ * one argument (an unknown there) and its neighbours stay theirs (`"#$Y" d` is never `#$Y d`). Any other launcher hands a
123
+ * word's text as `spell.word` spells it. One argv, as a list of the one.
123
124
  */
124
125
  export declare function spellLaunched(argv: readonly LexedWord[], args: readonly LaunchedArg[], spell: LaunchedSpelling): string[][];
125
126
  /** The argv index of the SCRIPT FILE a shell runs where its argv carries no text ({@link scriptSourceOf}). */
@@ -132,9 +133,10 @@ export interface StdinText {
132
133
  }
133
134
  /** What may arrive on a segment's standard input: each here-document (its descriptor is not read: every one counts) —
134
135
  * its body, literal when quoted or when nothing in it expands — each here-string (literal when its word is), `<` file
135
- * or `<&` descriptor written to no other descriptor, and the pipe it is reached through (`piped`) unless one of those
136
- * three replaces it. */
137
- export declare function stdinTextsOf(segment: LexedSegment, piped: StdinText | undefined): StdinText[];
136
+ * or `<&` descriptor written to no other descriptor, and what it inherits (`fed`: the pipe it is reached through, the
137
+ * pipe or the input of the group it stands in, the input of the shell running its text) unless one of those three
138
+ * replaces it. */
139
+ export declare function stdinTextsOf(segment: LexedSegment, fed: readonly StdinText[]): StdinText[];
138
140
  /** What one segment (its argv) writes into the pipe after it: the text a printer spells ({@link printedText}) when its
139
141
  * program word and every argument are literal, else only the segment as written. */
140
142
  export declare function pipedTextOf(segment: LexedSegment, argv: readonly LexedWord[]): StdinText;
@@ -1,6 +1,6 @@
1
1
  import { literalHeredocBody, literalTextOf } from "../../shell/lex.js";
2
2
  import { programBasename, readProgramPosition } from "./bash-program-position.js";
3
- import { implementationsOf, printedText, programRow, readArgRoles, selectedOptions } from "./program-read-model.js";
3
+ import { implementationRejects, implementationsOf, printedText, programRow, readArgRoles, selectedOptions, targetUserOf } from "./program-read-model.js";
4
4
  import { namesStandardInput } from "./safety.js";
5
5
  export function codeBodies(basename, args) {
6
6
  const row = programRow(basename);
@@ -77,17 +77,27 @@ export function launchedCommands(argv, pos = readProgramPosition(argv)) {
77
77
  const row = programRow(programBasename(argv[runs.launcher].folded));
78
78
  const words = argv.slice(runs.from).map((w) => w.folded);
79
79
  const at = (k) => runs.from + k;
80
- if (words.length === 0 || row?.launcher === undefined)
81
- return [{ args: [] }];
82
- if (row.launcher.shellText === "user")
80
+ if (row?.launcher?.shellText === "user")
83
81
  return userCommands(row, words, at);
82
+ const user = row === undefined ? undefined : targetUserOf(row, argv.slice(runs.launcher + 1, runs.from).map((w) => w.folded));
83
+ const as = user !== undefined ? { as: user } : {};
84
+ if (words.length === 0 || row?.launcher === undefined)
85
+ return [{ args: [], ...as }];
84
86
  if (row.launcher.shellText === "joined") {
85
- const joined = words.flatMap((w, k) => (w.startsWith("-") ? [] : [at(k)]));
86
- const attached = readArgRoles(row, words).flatMap((e) => (e.kind === "option" ? roleValue(e, "shell-text", words, at) ?? [] : []));
87
- const texts = [...(joined.length === 0 ? [] : [{ joined }]), ...attached];
88
- return texts.length === 0 ? [{ args: [] }] : texts.map((t) => ({ args: [{ fixed: "-c" }, t] }));
87
+ const texts = new Map();
88
+ for (const view of implementationsOf(row)) {
89
+ const events = readArgRoles(view, words);
90
+ const first = events.find((e) => e.kind === "operand" || e.kind === "dash-word-operand")?.at ?? words.length;
91
+ if (implementationRejects(row, view, words.slice(0, first)))
92
+ continue;
93
+ const joined = words.slice(first).map((_, k) => at(first + k));
94
+ const values = events.flatMap((e) => (e.kind === "option" || (e.kind === "value" && e.at < first) ? roleValue(e, "shell-text", words, at) ?? [] : []));
95
+ for (const t of [...(joined.length === 0 ? [] : [{ joined }]), ...values])
96
+ texts.set(JSON.stringify(t), t);
97
+ }
98
+ return texts.size === 0 ? [{ args: [], ...as }] : [...texts.values()].map((t) => ({ args: [{ fixed: "-c" }, t], ...as }));
89
99
  }
90
- return [{ args: [{ fixed: "-c" }, { joined: words.map((_, k) => at(k)), escaped: true }] }];
100
+ return [{ args: [{ fixed: "-c" }, { joined: words.map((_, k) => at(k)), escaped: true }], ...as }];
91
101
  }
92
102
  function roleValue(e, kind, words, at) {
93
103
  if (e.kind === "value" && e.role?.kind === kind)
@@ -109,16 +119,18 @@ function userCommands(row, words, at) {
109
119
  const named = own.flatMap((e) => valued(e, "command") ?? []).at(-1);
110
120
  const fromEnvironment = selectedOptions(view, stops ? words.slice(0, end) : words, "one-implementation").some((o) => o.mode === "names-program");
111
121
  const program = named !== undefined ? { program: named } : fromEnvironment ? { program: { spelled: "\"$SHELL\"" } } : {};
122
+ const target = targetUserOf(view, stops ? words.slice(0, end) : words.filter((_, k) => k !== user), { name: user === undefined ? "root" : words[user], dash: operands[0]?.dash !== undefined });
123
+ const runs = { ...program, ...(target !== undefined ? { as: target } : {}) };
112
124
  if (stops) {
113
- out.push({ ...program, args: user === undefined ? [] : words.slice(user + 1).map((_, k) => ({ at: at(user + 1 + k) })) });
125
+ out.push({ ...runs, args: user === undefined ? [] : words.slice(user + 1).map((_, k) => ({ at: at(user + 1 + k) })) });
114
126
  continue;
115
127
  }
116
128
  const rest = operands.filter((e) => user !== undefined && e.at > user).map((e) => ({ at: at(e.at) }));
117
129
  const texts = own.flatMap((e) => valued(e, "shell-text") ?? []);
118
130
  if (texts.length === 0)
119
- out.push({ ...program, args: rest });
131
+ out.push({ ...runs, args: rest });
120
132
  for (const text of texts)
121
- out.push({ ...program, args: [{ fixed: "-c" }, text, ...rest] });
133
+ out.push({ ...runs, args: [{ fixed: "-c" }, text, ...rest] });
122
134
  }
123
135
  return out;
124
136
  }
@@ -156,11 +168,10 @@ export function launchedInput(argv, pos, spell) {
156
168
  programWords.push(c.program.at);
157
169
  const words = c.args.map((x) => ("at" in x && x.from === undefined ? x.at : undefined));
158
170
  const args = asArgs(c.args);
159
- return asTexts(c.args).map((texts, i) => ({ shell, texts, args: args[i], words }));
171
+ return asTexts(c.args).map((texts, i) => ({ shell, texts, args: args[i], words, ...(c.as !== undefined ? { as: c.as } : {}) }));
160
172
  });
161
173
  return { shells, programWords, lines: [...lines] };
162
174
  }
163
- const LAUNCHED_READINGS_MAX = 64;
164
175
  function sudoEscaped(field) {
165
176
  return field.replace(/[^A-Za-z0-9_$-]/gu, (c) => `\\${c}`);
166
177
  }
@@ -168,20 +179,14 @@ function launchedSpellingOf(v) {
168
179
  return v.glob === true ? v.parts.map((p) => (p.pattern ? p.text : sudoEscaped(p.text))).join("") : sudoEscaped(v.spelled);
169
180
  }
170
181
  export function spellLaunched(argv, args, spell) {
171
- const choicesOf = (k) => {
182
+ const field = (k) => {
172
183
  const literal = literalTextOf(argv[k].pieces);
173
184
  if (literal !== undefined)
174
- return [sudoEscaped(literal)];
185
+ return sudoEscaped(literal);
175
186
  const values = spell.values?.(k);
176
- return values === undefined ? [argv[k].raw] : values.map(launchedSpellingOf);
187
+ return values?.length === 1 ? launchedSpellingOf(values[0]) : argv[k].raw;
177
188
  };
178
- const choices = new Map(args.flatMap((a) => ("joined" in a && a.escaped === true ? a.joined : [])).map((k) => [k, choicesOf(k)]));
179
- const distinct = new Map([...choices].map(([k, c]) => [argv[k].raw, c]));
180
- const combinations = [...distinct.values()].reduce((n, c) => n * c.length, 1);
181
- const readings = combinations <= LAUNCHED_READINGS_MAX
182
- ? [...distinct].reduce((acc, [w, c]) => acc.flatMap((r) => c.map((x) => new Map(r).set(w, x))), [new Map()]).map((r) => new Map([...choices.keys()].map((k) => [k, r.get(argv[k].raw)])))
183
- : Array.from({ length: [...choices.values()].reduce((n, c) => Math.max(n, c.length), 1) }, (_, i) => new Map([...choices].map(([k, c]) => [k, c[Math.min(i, c.length - 1)]])));
184
- return readings.map((reading) => args.map((a) => ("fixed" in a ? a.fixed : "at" in a ? spell.word(a.at, a.from ?? 0) : a.joined.map((k) => (a.escaped === true ? reading.get(k) : spell.word(k, 0))).join(" "))));
189
+ return [args.map((a) => ("fixed" in a ? a.fixed : "at" in a ? spell.word(a.at, a.from ?? 0) : a.joined.map((k) => (a.escaped === true ? field(k) : spell.word(k, 0))).join(" ")))];
185
190
  }
186
191
  export function scriptFileAt(basename, args) {
187
192
  const row = programRow(basename);
@@ -198,7 +203,7 @@ function scriptSourceOf(row, args) {
198
203
  return { stdin: true };
199
204
  return { file: first.at, stdin: options.some((e) => e.kind === "option" && e.reading === "speculative") };
200
205
  }
201
- export function stdinTextsOf(segment, piped) {
206
+ export function stdinTextsOf(segment, fed) {
202
207
  const out = [];
203
208
  let replacesPipe = false;
204
209
  for (const t of segment.tokens) {
@@ -212,7 +217,7 @@ export function stdinTextsOf(segment, piped) {
212
217
  replacesPipe = true;
213
218
  }
214
219
  }
215
- return piped === undefined || replacesPipe ? out : [piped, ...out];
220
+ return replacesPipe ? out : [...fed, ...out];
216
221
  }
217
222
  export function pipedTextOf(segment, argv) {
218
223
  const from = `${segment.text.trim()} |`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.47.0",
3
+ "version": "7.48.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -0,0 +1,252 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.48.0",
4
+ "from": "b29642cbf7daa459e48226114b2ac2132e855036",
5
+ "to": "7161f2a278ed51f0154de745fd7f6a4f8d349039",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [
10
+ {
11
+ "id": "F1",
12
+ "source": "behaviorFlips.summary",
13
+ "text": "BEHAVIOR NARROWING, open to veto (#1461): a word that a name's established value spells is read as that value at the program position and as an operand — a `Bash` allow no longer runs `P=rm; $P -rf <root>` without a card."
14
+ },
15
+ {
16
+ "id": "F2",
17
+ "source": "behaviorFlips.summary",
18
+ "text": "BEHAVIOR NARROWING, open to veto (#1461, completed): each field of a split unquoted value is a word of its own — named by its text, its `NAME=value` shape kept — for every reader, the removal guard included."
19
+ },
20
+ {
21
+ "id": "F7",
22
+ "source": "behaviorFlips.summary",
23
+ "text": "BEHAVIOR NARROWING (#1459): every launcher field asks the one value table — `su -c` / `runuser -c` / `watch` / `bash -c` hand every established value of a name, as `sudo -s` already did."
24
+ },
25
+ {
26
+ "id": "F8",
27
+ "source": "behaviorFlips.summary",
28
+ "text": "BEHAVIOR NARROWING (#1460): the combination bound counts distinct names and nothing is sampled past it — the words past the bound are handed on as written with their values as deny floors."
29
+ },
30
+ {
31
+ "id": "F9",
32
+ "source": "behaviorFlips.summary",
33
+ "text": "BEHAVIOR NARROWING (#1446): a launcher option cluster is read as getopt reads it, its command-line letter included."
34
+ },
35
+ {
36
+ "id": "F10",
37
+ "source": "behaviorFlips.summary",
38
+ "text": "BEHAVIOR NARROWING (#1452): `watch` declares its options (procps and BusyBox) and the words from its first operand on are the shell text; the exported `SHELL_WRAPPER_TABLE` `watch` row gains its option sets."
39
+ },
40
+ {
41
+ "id": "F11",
42
+ "source": "behaviorFlips.summary",
43
+ "text": "BEHAVIOR NARROWING (#1463): a shell run as another user starts with that user's home — a login shell stands in it, `HOME` there is the target's."
44
+ },
45
+ {
46
+ "id": "F12",
47
+ "source": "behaviorFlips.summary",
48
+ "text": "BEHAVIOR NARROWING, open to veto (#1424): standard input flows into a group and into an inner shell; a process-substitution script and a stream-fed shell are scripts built when the command runs — also for a harmless text."
49
+ },
50
+ {
51
+ "id": "F13",
52
+ "source": "behaviorFlips.summary",
53
+ "text": "BEHAVIOR NARROWING (#1466): the judge sees every name bash opens — a part of a pattern word carries the word's widened pattern, a malformed bracket unit is a member as bash reads it, a field forms its pattern once it has fully expanded."
54
+ },
55
+ {
56
+ "id": "F14",
57
+ "source": "behaviorFlips.summary",
58
+ "text": "BEHAVIOR NARROWING (#1471): session rules with a bucket that is not a list of strings are refused — a bare `\"rm\"` was stored as `[\"r\",\"m\"]`."
59
+ },
60
+ {
61
+ "id": "F15",
62
+ "source": "behaviorFlips.summary",
63
+ "text": "BEHAVIOR NARROWING (#1400): a protocol name with an empty tool segment (`mcp__srv__`) is judged `unmintable_segment`, not `current` — a rule that guarded nothing in silence is refused or announced."
64
+ },
65
+ {
66
+ "id": "F17",
67
+ "source": "behaviorFlips.summary",
68
+ "text": "BEHAVIOR NARROWING (#1442): a `readDenyPatterns` row that opens with `/` is rooted — it names that one absolute location (placed where its literal head leads through a link), not that tail at any depth."
69
+ }
70
+ ],
71
+ "closedSetMembers": [],
72
+ "closedSetsUnreadable": [],
73
+ "typeKeys": [],
74
+ "typeKeysUnreadableArms": [],
75
+ "typeKeysFlattenedCount": 228,
76
+ "signatures": [],
77
+ "declarationMoves": [],
78
+ "mintMoves": [],
79
+ "criteriaObserves": [
80
+ {
81
+ "kind": "taskEventField",
82
+ "name": "tool_end.gate",
83
+ "assertions": [
84
+ "G1.a",
85
+ "G2.a",
86
+ "G2.c",
87
+ "G4.a",
88
+ "G4.b",
89
+ "G5.a",
90
+ "G5.c",
91
+ "G6.a",
92
+ "G7.a",
93
+ "G8.a",
94
+ "G8.b"
95
+ ]
96
+ },
97
+ {
98
+ "kind": "taskEventField",
99
+ "name": "tool_end.isError",
100
+ "assertions": [
101
+ "G12.a",
102
+ "G12.b"
103
+ ]
104
+ },
105
+ {
106
+ "kind": "taskEventField",
107
+ "name": "tool_end.output",
108
+ "assertions": [
109
+ "G12.a",
110
+ "G12.b"
111
+ ]
112
+ },
113
+ {
114
+ "kind": "typeMember",
115
+ "name": "AskRequest.askClass",
116
+ "assertions": [
117
+ "G1.a",
118
+ "G1.b",
119
+ "G1.c",
120
+ "G1.d",
121
+ "G2.a",
122
+ "G3.a",
123
+ "G3.b",
124
+ "G3.c",
125
+ "G3.d",
126
+ "G4.a",
127
+ "G4.c",
128
+ "G5.a",
129
+ "G5.b",
130
+ "G5.c",
131
+ "G5.d",
132
+ "G6.a",
133
+ "G6.b",
134
+ "G6.c",
135
+ "G7.a",
136
+ "G7.b",
137
+ "G8.c"
138
+ ]
139
+ },
140
+ {
141
+ "kind": "typeMember",
142
+ "name": "AskRequest.classes",
143
+ "assertions": [
144
+ "G2.b"
145
+ ]
146
+ },
147
+ {
148
+ "kind": "typeMember",
149
+ "name": "AskRequest.message",
150
+ "assertions": [
151
+ "G1.c",
152
+ "G3.b",
153
+ "G6.b",
154
+ "G7.b"
155
+ ]
156
+ },
157
+ {
158
+ "kind": "typeMember",
159
+ "name": "GateOutcome.disposition",
160
+ "assertions": [
161
+ "G1.b",
162
+ "G2.b",
163
+ "G2.c",
164
+ "G3.a",
165
+ "G3.c",
166
+ "G3.d",
167
+ "G4.b",
168
+ "G4.c",
169
+ "G5.b",
170
+ "G5.d",
171
+ "G6.c",
172
+ "G8.a",
173
+ "G8.b",
174
+ "G8.c"
175
+ ]
176
+ },
177
+ {
178
+ "kind": "typeMember",
179
+ "name": "SessionPolicyError.code",
180
+ "assertions": [
181
+ "G9.b",
182
+ "G10.a"
183
+ ]
184
+ },
185
+ {
186
+ "kind": "typeMember",
187
+ "name": "SessionPolicyError.detail.verdict",
188
+ "assertions": [
189
+ "G10.a"
190
+ ]
191
+ },
192
+ {
193
+ "kind": "typeMember",
194
+ "name": "SessionPolicyStore.getRules",
195
+ "assertions": [
196
+ "G9.a"
197
+ ]
198
+ },
199
+ {
200
+ "kind": "typeMember",
201
+ "name": "SessionPolicyStore.putRules",
202
+ "assertions": [
203
+ "G9.a",
204
+ "G9.b"
205
+ ]
206
+ },
207
+ {
208
+ "kind": "typeMember",
209
+ "name": "TaskResult.terminal.code",
210
+ "assertions": [
211
+ "G10.b"
212
+ ]
213
+ },
214
+ {
215
+ "kind": "typeMember",
216
+ "name": "TaskResult.terminal.message",
217
+ "assertions": [
218
+ "G10.b"
219
+ ]
220
+ },
221
+ {
222
+ "kind": "typeMember",
223
+ "name": "WorkflowRun.error",
224
+ "assertions": [
225
+ "G11.a",
226
+ "G11.b"
227
+ ]
228
+ },
229
+ {
230
+ "kind": "typeMember",
231
+ "name": "WorkflowRun.result",
232
+ "assertions": [
233
+ "G11.b"
234
+ ]
235
+ },
236
+ {
237
+ "kind": "typeMember",
238
+ "name": "WorkflowRun.status",
239
+ "assertions": [
240
+ "G11.a"
241
+ ]
242
+ }
243
+ ],
244
+ "consumersHint": {
245
+ "cli": [],
246
+ "server": [],
247
+ "sdk": [],
248
+ "note": "scanned cli@bad8fa4b server@a49a0616 sdk@ae10bec — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it"
249
+ },
250
+ "breakingCount": 0,
251
+ "breakingItems": []
252
+ }