@sema-agent/core 7.56.0 → 7.57.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 (173) hide show
  1. package/CHANGELOG.md +541 -0
  2. package/LICENSE +1 -1
  3. package/dist/agents/org-delivery-gate.js +3 -4
  4. package/dist/agents/peer-held-queue.d.ts +69 -49
  5. package/dist/agents/peer-held-queue.js +255 -106
  6. package/dist/agents/peer-session-drain.js +63 -43
  7. package/dist/agents/send-message-tool.js +3 -2
  8. package/dist/agents/subagent.js +8 -8
  9. package/dist/brain/anthropic.js +16 -8
  10. package/dist/brain/open-responses.js +10 -8
  11. package/dist/brain/openai.js +11 -9
  12. package/dist/brain/provider-dialect.d.ts +3 -2
  13. package/dist/brain/provider-dialect.js +2 -2
  14. package/dist/brain/request-params.d.ts +6 -0
  15. package/dist/brain/request-params.js +14 -1
  16. package/dist/brain/stream-engine.d.ts +9 -0
  17. package/dist/brain/stream-engine.js +24 -10
  18. package/dist/brain/stream-shared.d.ts +5 -4
  19. package/dist/brain/stream-shared.js +4 -4
  20. package/dist/core/allow-entry.d.ts +3 -10
  21. package/dist/core/allow-entry.js +17 -25
  22. package/dist/core/approval-object.d.ts +21 -5
  23. package/dist/core/approval-object.js +44 -17
  24. package/dist/core/ask-origin.d.ts +2 -2
  25. package/dist/core/auto-compaction.d.ts +71 -6
  26. package/dist/core/auto-compaction.js +42 -26
  27. package/dist/core/background-agent-store.d.ts +6 -2
  28. package/dist/core/background-agent-store.js +1 -1
  29. package/dist/core/checkpoint-requirements.d.ts +7 -1
  30. package/dist/core/checkpoint-requirements.js +2 -0
  31. package/dist/core/checkpoint-store.d.ts +17 -29
  32. package/dist/core/checkpoint-store.js +19 -11
  33. package/dist/core/cross-boundary-taint.js +4 -2
  34. package/dist/core/engine-notice.d.ts +3 -2
  35. package/dist/core/engine-notice.js +6 -0
  36. package/dist/core/file-history-store.d.ts +14 -8
  37. package/dist/core/file-history-store.js +14 -4
  38. package/dist/core/gate-lanes.js +2 -3
  39. package/dist/core/hooks.d.ts +2 -2
  40. package/dist/core/mailbox-store.d.ts +62 -1
  41. package/dist/core/mailbox-store.js +59 -0
  42. package/dist/core/mcp-name-judge.d.ts +46 -0
  43. package/dist/core/mcp-name-judge.js +61 -0
  44. package/dist/core/mcp-probe.d.ts +1 -1
  45. package/dist/core/mcp-probe.js +2 -5
  46. package/dist/core/mcp.js +10 -30
  47. package/dist/core/memory-engine/consolidation-driver.js +4 -3
  48. package/dist/core/memory-engine/distiller.js +26 -17
  49. package/dist/core/memory-engine/layout.js +5 -2
  50. package/dist/core/park-selfcheck.js +2 -0
  51. package/dist/core/permission-rule-consent.d.ts +5 -3
  52. package/dist/core/permission-rule-consent.js +20 -15
  53. package/dist/core/permission-rule-model.d.ts +43 -2
  54. package/dist/core/permission-rule-model.js +52 -8
  55. package/dist/core/permission-rule-org.js +1 -1
  56. package/dist/core/protocol-http-auth.d.ts +16 -2
  57. package/dist/core/protocol-http-auth.js +42 -11
  58. package/dist/core/protocol-naming.d.ts +20 -0
  59. package/dist/core/protocol-naming.js +20 -3
  60. package/dist/core/read-only-shell.d.ts +20 -5
  61. package/dist/core/read-only-shell.js +29 -14
  62. package/dist/core/remote-env.d.ts +37 -1
  63. package/dist/core/remote-env.js +11 -0
  64. package/dist/core/resume-startup-cause.d.ts +24 -2
  65. package/dist/core/resume-startup-cause.js +20 -1
  66. package/dist/core/roles.js +50 -7
  67. package/dist/core/runner/compact-session.d.ts +10 -0
  68. package/dist/core/runner/compact-session.js +202 -0
  69. package/dist/core/runner/compaction-call-options.d.ts +2 -2
  70. package/dist/core/runner/compaction-seams.d.ts +21 -1
  71. package/dist/core/runner/compaction-seams.js +11 -8
  72. package/dist/core/runner/contracts.d.ts +23 -16
  73. package/dist/core/runner/derived-route-fallback.d.ts +13 -0
  74. package/dist/core/runner/derived-route-fallback.js +6 -0
  75. package/dist/core/runner/env-capability-census.d.ts +3 -2
  76. package/dist/core/runner/env-capability-census.js +9 -3
  77. package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
  78. package/dist/core/runner/permission-rule-lanes.js +17 -11
  79. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  82. package/dist/core/runner/prepare-config-doors.js +4 -3
  83. package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
  84. package/dist/core/runner/prepare-hands-readface.js +2 -0
  85. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  86. package/dist/core/runner/prepare-park-ask.js +2 -2
  87. package/dist/core/runner/prepare-policy-chain.js +2 -4
  88. package/dist/core/runner/prepare-task.d.ts +4 -1
  89. package/dist/core/runner/prepare-task.js +23 -27
  90. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
  91. package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
  92. package/dist/core/runner/rewind-files.d.ts +2 -5
  93. package/dist/core/runner/rewind-files.js +9 -51
  94. package/dist/core/runner/run-leg.js +10 -21
  95. package/dist/core/runner/run-notification-lane.d.ts +2 -0
  96. package/dist/core/runner/run-notification-lane.js +2 -2
  97. package/dist/core/runner/run-recovery-lanes.js +3 -4
  98. package/dist/core/runner/run-settle-and-teardown.js +5 -11
  99. package/dist/core/runner/run-terminal-adoption.js +3 -4
  100. package/dist/core/runner/run-turn-boundary.js +3 -5
  101. package/dist/core/runner/runtask.d.ts +8 -1
  102. package/dist/core/runner/runtask.js +28 -34
  103. package/dist/core/runner/send-message-continuation.js +1 -1
  104. package/dist/core/runner/session-verb-doors.d.ts +28 -0
  105. package/dist/core/runner/session-verb-doors.js +48 -0
  106. package/dist/core/runner/session-write-ledger.d.ts +30 -0
  107. package/dist/core/runner/session-write-ledger.js +101 -0
  108. package/dist/core/runner/stream-settle-backstop.js +11 -14
  109. package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
  110. package/dist/core/runner/sub-agent-deny-lists.js +21 -10
  111. package/dist/core/runner/teardown-bounded.d.ts +1 -0
  112. package/dist/core/runner/teardown-bounded.js +1 -0
  113. package/dist/core/runner-deps.d.ts +2 -2
  114. package/dist/core/secret-env.d.ts +4 -0
  115. package/dist/core/secret-env.js +3 -0
  116. package/dist/core/session-policy-store.d.ts +7 -2
  117. package/dist/core/session-policy-store.js +1 -1
  118. package/dist/core/shell-wrapper-projection.d.ts +1 -1
  119. package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  121. package/dist/core/store-contracts/file-history-store-contract.js +9 -0
  122. package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
  123. package/dist/core/task-registry-agent.d.ts +8 -4
  124. package/dist/core/task-registry-agent.js +14 -8
  125. package/dist/core/task-registry-shared.d.ts +35 -6
  126. package/dist/core/task-registry-shared.js +11 -0
  127. package/dist/core/task-registry.d.ts +1 -1
  128. package/dist/core/task-registry.js +1 -1
  129. package/dist/core/tool-catalog-entries.d.ts +6 -0
  130. package/dist/core/tool-catalog-entries.js +1 -0
  131. package/dist/core/tool-name-aliases.d.ts +32 -2
  132. package/dist/core/tool-name-aliases.js +23 -0
  133. package/dist/core/tool-name-readings.d.ts +4 -0
  134. package/dist/core/tool-name-readings.js +1 -0
  135. package/dist/core/tool-policy.d.ts +10 -6
  136. package/dist/core/tool-policy.js +4 -7
  137. package/dist/core/trace.d.ts +4 -1
  138. package/dist/core/trace.js +2 -1
  139. package/dist/core/untrusted-egress.d.ts +20 -0
  140. package/dist/core/untrusted-egress.js +77 -3
  141. package/dist/engine/harness/agent-harness.d.ts +7 -5
  142. package/dist/engine/harness/agent-harness.js +11 -2
  143. package/dist/engine/loop/agent-loop.d.ts +1 -4
  144. package/dist/engine/loop/agent-loop.js +26 -4
  145. package/dist/index.d.ts +12 -7
  146. package/dist/index.js +7 -4
  147. package/dist/internal/harness.d.ts +2 -1
  148. package/dist/internal/harness.js +1 -0
  149. package/dist/primitives/credential-ref.d.ts +89 -0
  150. package/dist/primitives/credential-ref.js +125 -0
  151. package/dist/primitives/index.d.ts +2 -0
  152. package/dist/primitives/index.js +1 -0
  153. package/dist/stores/file/checkpoint-store.d.ts +0 -4
  154. package/dist/stores/file/checkpoint-store.js +0 -1
  155. package/dist/stores/file/file-history-store.js +12 -4
  156. package/dist/stores/file/mailbox-store.d.ts +2 -1
  157. package/dist/stores/file/mailbox-store.js +26 -1
  158. package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
  159. package/dist/tools/fs/bash-readonly-classifier.js +5 -10
  160. package/dist/tools/fs/fs-bash.js +10 -3
  161. package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
  162. package/dist/tools/fs/pattern-kill-guard.js +68 -36
  163. package/dist/tools/fs/shell-read-check.d.ts +10 -0
  164. package/dist/tools/fs/shell-read-check.js +7 -1
  165. package/dist/tools/fs/shell-read-identity.d.ts +12 -9
  166. package/dist/tools/fs/shell-read-identity.js +9 -4
  167. package/dist/tools/web.js +9 -7
  168. package/package.json +1 -1
  169. package/test/export-surface.snapshot.json +79 -3
  170. package/wire-changes/core-stores-sql@7.57.0.json +29 -0
  171. package/wire-changes/core@7.57.0.json +1487 -0
  172. package/wire-changes/mods-kernel@7.57.0.json +29 -0
  173. package/wire-changes/primitives@7.57.0.json +245 -0
@@ -1,15 +1,18 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { errorResult } from "../../core/tools.js";
4
- import { isRemoteExecutionEnv } from "../../core/remote-env.js";
4
+ import { processTableOf } from "../../core/remote-env.js";
5
5
  import { inheritedHostEnv } from "../../core/secret-env.js";
6
6
  import { inlineUntrusted } from "../../core/untrusted-text.js";
7
7
  import { planShellReads } from "./shell-read-plan.js";
8
8
  import { programArgs } from "./shell-read-write-targets.js";
9
9
  const PROBE_TIMEOUT_MS = 5_000;
10
10
  const ANCESTOR_HOPS_MAX = 128;
11
- const SHELL_PID_WORD = /^\$(?:\$|\{\$\}|BASHPID|\{BASHPID\})$/;
11
+ const SHELL_PID_SPELLING = String.raw `\$(?:\$|\{\$\})`;
12
+ const SHELL_PID_WORD = new RegExp(`^${SHELL_PID_SPELLING}$`);
13
+ const SHELL_PID_EXPANSION = new RegExp(SHELL_PID_SPELLING, "g");
12
14
  const NAME_SELECTION_KEEPS_ANCESTORS = process.platform === "linux" ? [] : ["-a"];
15
+ const OPTIONS_END_AT_FIRST_OPERAND = process.platform !== "linux";
13
16
  const SIGNAL_ONLY_FIRST_ARGUMENT = process.platform !== "linux";
14
17
  const HANDLER_SELECTS_BY_SIGNAL = process.platform === "linux";
15
18
  const LONG_OPTIONS = new Map([
@@ -54,21 +57,28 @@ const isSignalWord = (w) => {
54
57
  const m = SIGNAL_WORD.exec(w);
55
58
  return m !== null && (m[1] === undefined || SIGNAL_NAMES.has(m[1].toUpperCase()));
56
59
  };
57
- function selectorWords(allWords, killer) {
60
+ function readSelection(allWords, killer) {
58
61
  const signalAt = killer !== "pkill" ? -1 : SIGNAL_ONLY_FIRST_ARGUMENT ? (isSignalWord(allWords[0] ?? "") ? 0 : -1) : allWords.findIndex(isSignalWord);
59
62
  const signalWord = signalAt < 0 ? undefined : allWords[signalAt].slice(1);
60
63
  const words = signalAt < 0 ? allWords : allWords.filter((_, i) => i !== signalAt);
61
64
  const out = [];
65
+ const parentLists = [];
62
66
  let ended = false;
63
67
  let requireHandler = false;
68
+ let negated = false;
64
69
  let signalOption;
65
70
  let valueOf;
71
+ const valueGiven = (key, value) => {
72
+ if (key === "P")
73
+ parentLists.push(value.split(","));
74
+ };
66
75
  for (const w of words) {
67
76
  if (valueOf !== undefined) {
68
- if (valueOf === "ask")
77
+ if (valueOf.fate === "ask")
69
78
  out.push(w);
70
- else if (valueOf === "signal")
79
+ else if (valueOf.fate === "signal")
71
80
  signalOption = w;
81
+ valueGiven(valueOf.key, w);
72
82
  valueOf = undefined;
73
83
  continue;
74
84
  }
@@ -82,60 +92,75 @@ function selectorWords(allWords, killer) {
82
92
  if (key !== undefined && (takesValueKey(key) || longValue === undefined)) {
83
93
  if (key === "H")
84
94
  requireHandler = true;
95
+ if (key === "v")
96
+ negated = true;
85
97
  const fate = fateOfKey(key);
86
98
  if (fate === "ask")
87
99
  out.push(w);
88
100
  if (takesValueKey(key) && longValue === undefined)
89
- valueOf = fate;
90
- else if (fate === "signal" && longValue !== undefined)
91
- signalOption = longValue;
101
+ valueOf = { fate, key };
102
+ else if (longValue !== undefined) {
103
+ if (fate === "signal")
104
+ signalOption = longValue;
105
+ valueGiven(key, longValue);
106
+ }
92
107
  continue;
93
108
  }
94
109
  const letters = bundledLetters(w);
95
110
  if (letters === undefined) {
96
111
  out.push(w);
112
+ if (OPTIONS_END_AT_FIRST_OPERAND && (w === "-" || !w.startsWith("-")))
113
+ ended = true;
97
114
  continue;
98
115
  }
99
116
  if (letters.includes("H"))
100
117
  requireHandler = true;
118
+ if (letters.includes("v"))
119
+ negated = true;
101
120
  const last = letters[letters.length - 1];
102
121
  const fate = fateOfKey(last);
103
122
  const kept = letters.filter((ch) => fateOfKey(ch) === "ask").join("");
104
123
  const attached = VALUED_LETTERS.has(last) ? w.slice(1 + letters.length) : "";
105
124
  if (kept !== "")
106
125
  out.push(`-${kept}${fate === "ask" ? attached : ""}`);
107
- if (VALUED_LETTERS.has(last) && attached === "")
108
- valueOf = fate;
126
+ if (VALUED_LETTERS.has(last)) {
127
+ if (attached === "")
128
+ valueOf = { fate, key: last };
129
+ else
130
+ valueGiven(last, attached);
131
+ }
109
132
  }
110
133
  const signal = signalOption ?? signalWord;
111
- return HANDLER_SELECTS_BY_SIGNAL && requireHandler && signal !== undefined ? ["--signal", signal, ...out] : out;
134
+ return { words: HANDLER_SELECTS_BY_SIGNAL && requireHandler && signal !== undefined ? ["--signal", signal, ...out] : out, parentLists, negated };
112
135
  }
113
- const negatesSelection = (words) => words.some((w) => w === "--inverse" || (bundledLetters(w)?.includes("v") ?? false));
114
- function selectsOnlyShellChildren(words) {
115
- return words.some((w, i) => {
116
- if (w === "-P" || w === "--parent")
117
- return SHELL_PID_WORD.test(words[i + 1] ?? "");
118
- const attached = /^(?:-P|--parent=)(.+)$/.exec(w);
119
- return attached !== null && SHELL_PID_WORD.test(attached[1]);
120
- });
136
+ const LAST_PARENT_LIST_WINS = process.platform === "linux";
137
+ function boundedToShellChildren(read, unread) {
138
+ if (unread !== undefined)
139
+ return false;
140
+ const standing = LAST_PARENT_LIST_WINS ? read.parentLists.slice(-1) : read.parentLists;
141
+ return standing.length > 0 && standing.every((list) => list.every((pid) => SHELL_PID_WORD.test(pid)));
121
142
  }
122
143
  const literalName = (name) => name.replace(/[\\.[\]{}()*+?^$|]/g, "\\$&");
123
144
  function wordsOf(segment) {
124
145
  const args = programArgs(segment);
125
- const rewritten = args.find((a) => a.rewritten === true);
126
- const computed = segment.reentry === "argv" ? "its arguments arrive on a stream" : rewritten === undefined ? undefined : `\`${inlineUntrusted(rewritten.raw, 60)}\` is expanded by the shell when the command runs`;
127
- return { words: args.map((a) => a.spelled), ...(computed !== undefined ? { computed } : {}) };
146
+ const expands = (a) => `\`${inlineUntrusted(a.raw, 60)}\` is expanded by the shell when the command runs`;
147
+ const stream = segment.reentry === "argv" ? "its arguments arrive on a stream" : undefined;
148
+ const opaque = args.find((a) => a.rewritten === true && /[$`*?[~{\\]/.test(a.raw.replace(SHELL_PID_EXPANSION, "0")));
149
+ const first = args.find((a) => a.rewritten === true);
150
+ const unread = stream ?? (opaque === undefined ? undefined : expands(opaque));
151
+ const computed = unread ?? (first === undefined ? undefined : expands(first));
152
+ return { words: args.map((a) => a.spelled), ...(computed !== undefined ? { computed } : {}), ...(unread !== undefined ? { unread } : {}) };
128
153
  }
129
154
  const shown = (segment) => inlineUntrusted(segment.text, 200);
130
155
  function readPkill(segment) {
131
- const { words, computed } = wordsOf(segment);
132
- if (selectsOnlyShellChildren(words)) {
133
- return negatesSelection(words) ? { text: shown(segment), probes: [], computed: "`-v` negates the selection, so `-P` of the shell's pid no longer limits it to the shell's children" } : undefined;
156
+ const { words, computed, unread } = wordsOf(segment);
157
+ const read = readSelection(words, "pkill");
158
+ if (boundedToShellChildren(read, unread)) {
159
+ return read.negated ? { text: shown(segment), probes: [], computed: "the selection is negated (`-v` / `--inverse`), so `-P` of the shell's pid no longer limits it to the shell's children" } : undefined;
134
160
  }
135
161
  if (computed !== undefined)
136
162
  return { text: shown(segment), probes: [], computed };
137
- const selectors = selectorWords(words, "pkill");
138
- return selectors.length === 0 ? undefined : { text: shown(segment), probes: [selectors] };
163
+ return read.words.length === 0 ? undefined : { text: shown(segment), probes: [read.words] };
139
164
  }
140
165
  const OPT_FLAG = { does: "flag" };
141
166
  const OPT_FILTER = { does: "filter" };
@@ -157,7 +182,6 @@ const KILLALL_OPTIONS = new Map([
157
182
  ["-v", OPT_FLAG], ["--verbose", OPT_FLAG], ["-V", OPT_FLAG], ["--version", OPT_FLAG], ["-w", OPT_FLAG], ["--wait", OPT_FLAG], ["-d", OPT_FLAG], ["-z", OPT_FLAG], ["--help", OPT_FLAG],
158
183
  ]);
159
184
  const takesValue = (opt) => opt !== undefined && (opt.does === "select" || opt.does === "context" || opt.does === "filter" || opt.does === "valued");
160
- const OPTIONS_END_AT_FIRST_NAME = process.platform !== "linux";
161
185
  const KILLALL_VALUED_LETTERS = new Set([...KILLALL_OPTIONS].filter(([spelling, opt]) => !spelling.startsWith("--") && takesValue(opt)).map(([spelling]) => spelling.slice(1)));
162
186
  function optionsOf(w) {
163
187
  if (w.startsWith("--")) {
@@ -185,7 +209,7 @@ function readKillallWords(words) {
185
209
  }
186
210
  if (!w.startsWith("-") || w === "-") {
187
211
  names.push(w);
188
- if (OPTIONS_END_AT_FIRST_NAME) {
212
+ if (OPTIONS_END_AT_FIRST_OPERAND) {
189
213
  names.push(...words.slice(i + 1));
190
214
  break;
191
215
  }
@@ -238,7 +262,7 @@ function readSelector(segment) {
238
262
  const names = read.words.filter((w, i) => !w.startsWith("-") && read.words[i - 1] !== "-o");
239
263
  return { text: shown(segment), probes: names.map((n) => [...NAME_SELECTION_KEEPS_ANCESTORS, "-x", literalName(n)]) };
240
264
  }
241
- return { text: shown(segment), probes: [selectorWords(read.words, "pgrep")] };
265
+ return { text: shown(segment), probes: [readSelection(read.words, "pgrep").words] };
242
266
  }
243
267
  const SELECTORS = new Set(["pgrep", "pidof"]);
244
268
  const EVERY_PROCESS_PID = /^-0*1$/;
@@ -358,12 +382,8 @@ const SENTENCE = {
358
382
  computed: (f) => `Error (Bash): refusing to run \`${f.text}\` — which processes it selects is decided when the command runs (${f.why}), so it cannot first be checked against this agent's own process and the processes above it. Write the pattern into the command as a literal, ${WAY_OUT}.`,
359
383
  unavailable: (f) => `Error (Bash): refusing to run \`${f.text}\` — the check that its selection holds neither this agent's own process nor a process above it could not run (${f.why}). Stop the process by its PID, or target your own children with \`pkill -P $$ ...\`.`,
360
384
  };
361
- export async function patternKillRefusal(command, env, cwd, signal) {
362
- if (process.platform === "win32" || isRemoteExecutionEnv(env))
363
- return undefined;
364
- const readings = patternKillReadings(command, cwd);
365
- if (readings.length === 0)
366
- return undefined;
385
+ const UNDECLARED_TABLE = "the execution environment does not declare whether its commands run on this agent's process table — `capabilities.sharesProcessTable` is missing or not a boolean";
386
+ async function findingsOnThisTable(readings, signal) {
367
387
  const found = [];
368
388
  const asked = await Promise.all(readings.map(async (r) => ({ r, answers: await Promise.all(r.probes.map((p) => selected(p, signal))) })));
369
389
  let ancestry;
@@ -389,6 +409,18 @@ export async function patternKillRefusal(command, env, cwd, signal) {
389
409
  found.push({ kind: "hit", text: r.text, pid, own: pid === process.pid });
390
410
  }
391
411
  }
412
+ return found;
413
+ }
414
+ export async function patternKillRefusal(command, env, cwd, signal) {
415
+ if (process.platform === "win32")
416
+ return undefined;
417
+ const readings = patternKillReadings(command, cwd).filter((r) => r.probes.length > 0 || r.computed !== undefined || r.everyProcess === true);
418
+ if (readings.length === 0)
419
+ return undefined;
420
+ const table = processTableOf(env);
421
+ if (table === "other")
422
+ return undefined;
423
+ const found = table === "this" ? await findingsOnThisTable(readings, signal) : [{ kind: "unavailable", text: readings[0].text, why: UNDECLARED_TABLE }];
392
424
  const f = found.find((x) => x.kind === "hit") ?? found.find((x) => x.kind === "computed") ?? found[0];
393
425
  if (f === undefined || signal?.aborted === true)
394
426
  return undefined;
@@ -20,6 +20,7 @@
20
20
  import type { ExecutionEnv } from "../../internal/harness-types.js";
21
21
  import { violationDetails } from "./safety.js";
22
22
  import { type ShellReadPlan } from "./shell-read-plan.js";
23
+ import { type ReadLanding } from "./shell-read-identity.js";
23
24
  import { type HomeViewSource } from "./read-deny-home.js";
24
25
  import { type EstablishedRead, type ShellReadBoundary, type ShellReadDisposition, type ShellReadFinding, type ShellReadSeat } from "./shell-read-judge.js";
25
26
  import type { ReadRootGrantCandidate } from "../../core/tool-spec.js";
@@ -169,9 +170,18 @@ export type ShellReadAdmission = (toolName: string, toolCallId: string | undefin
169
170
  isError: true;
170
171
  details?: unknown;
171
172
  } | undefined>;
173
+ /** Where the station's last non-refused judgment of a call found its reads land (`readLandingsOf`): the judgment its
174
+ * admission holds for the call id — the one the spawn re-establishes — read without consuming it. `undefined` ⇒ the
175
+ * station holds none (a refused judgment records nothing). Its readers take it right where the latest judgment is a
176
+ * non-refused one of the bytes they hold: the park when the filed bytes are the judged ones, the resumed leg's execution
177
+ * station after its belt. A judgment that settles after a newer one replaced it is what the spawn would re-establish too:
178
+ * the spawn then refuses bytes the entry does not describe, and a row bound to it vetoes the resume wherever the two
179
+ * commands' reads differ (fail-closed either way). */
180
+ export type ShellReadLandings = (toolCallId: string) => readonly ReadLanding[] | undefined;
172
181
  export interface ShellReadStation {
173
182
  readonly check: ShellReadCheck;
174
183
  readonly admit: ShellReadAdmission;
184
+ readonly landings: ShellReadLandings;
175
185
  }
176
186
  export interface ShellReadStationOptions {
177
187
  readonly env: ExecutionEnv;
@@ -7,6 +7,7 @@ import { NOT_AUTO_ALLOWED, declinedProgramSentence } from "./bash-program-positi
7
7
  import { bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
8
8
  import { planShellReads } from "./shell-read-plan.js";
9
9
  import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
10
+ import { readLandingsOf } from "./shell-read-identity.js";
10
11
  import { declaredReadDenyHomes, homeViewsAt } from "./read-deny-home.js";
11
12
  import { anyOptionWord, programArgs } from "./shell-read-write-targets.js";
12
13
  import { shellReadFaceWords } from "./shell-read-face.js";
@@ -395,6 +396,7 @@ export function createShellReadStation(opts) {
395
396
  };
396
397
  const admissions = new Map();
397
398
  const remember = (toolCallId, a) => {
399
+ admissions.delete(toolCallId);
398
400
  if (admissions.size >= ADMISSION_MAX) {
399
401
  const oldest = admissions.keys().next().value;
400
402
  if (oldest !== undefined)
@@ -478,5 +480,9 @@ export function createShellReadStation(opts) {
478
480
  const r = refusalOf(toolName, identity, a.boundary, []);
479
481
  return refused(errorResult(r.sentence, containmentDetails(changed.map((c) => c.target).filter((t) => t !== undefined))));
480
482
  };
481
- return { check, admit };
483
+ const landings = (toolCallId) => {
484
+ const a = admissions.get(toolCallId);
485
+ return a !== undefined ? readLandingsOf(a.established) : undefined;
486
+ };
487
+ return { check, admit, landings };
482
488
  }
@@ -1,18 +1,21 @@
1
- /**
2
- * The pre-spawn identity comparison: which established reads of one plan resolve somewhere else now.
3
- *
4
- * Compared PER CANDIDATE, never as one pooled set of targets: a candidate is keyed by where it stands in the plan
5
- * (segment, nested plan) and by what it is (deny-only or a read, provenance, spelling, expansion, raw word), and its
6
- * set of established targets is compared with the same candidate's set from the re-establishment. A pooled set let
7
- * one candidate's targets hide another's move — `head linkx` retargeted onto a path some other word's pattern already
8
- * matched, or onto a home shadow's target, left the pool unchanged and the call was admitted unjudged.
9
- */
10
1
  import type { EstablishedPlan } from "./shell-read-establish.js";
11
2
  import type { ObservedEnvironment } from "./shell-read-environment.js";
12
3
  import type { EstablishedRead } from "./shell-read-judge.js";
13
4
  import { type HomeViewsCarrier } from "./read-deny-home.js";
14
5
  /** Every read of either establishment whose candidate's settled target set differs between the two. */
15
6
  export declare function movedReads(previous: EstablishedPlan, again: EstablishedPlan): EstablishedRead[];
7
+ /** One read candidate's landing: its key and where it settled, each as a sha256 hex digest (the key's raw tuple joins on
8
+ * NUL and follows the command's length; a pattern lands on up to thousands of entries), with the raw word and the
9
+ * settled targets kept for the sentence that names a moved one. */
10
+ export interface ReadLanding {
11
+ readonly key: string;
12
+ readonly landing: string;
13
+ readonly raw: string;
14
+ readonly targets: readonly string[];
15
+ }
16
+ /** Every read candidate of the establishment (nested plans included) with where it landed, in plan order. A candidate
17
+ * that settled nowhere (a lexical or unestablishable read) lands on the empty set — placed later, that is a move. */
18
+ export declare function readLandingsOf(e: EstablishedPlan): ReadLanding[];
16
19
  /**
17
20
  * The establishment with every read that landed carrying its HOME VIEW (read-deny-home.ts) among its spellings, nested
18
21
  * plans included: the deny judge then places a home-anchored row wherever the target IS below the home, whatever it is
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { homeViewsOf } from "./read-deny-home.js";
2
3
  export function movedReads(previous, again) {
3
4
  const before = targetsByCandidate(previous);
@@ -6,12 +7,19 @@ export function movedReads(previous, again) {
6
7
  for (const key of new Set([...before.keys(), ...after.keys()])) {
7
8
  const b = before.get(key);
8
9
  const a = after.get(key);
9
- if (b !== undefined && a !== undefined && sameTargets(b.targets, a.targets))
10
+ if (b !== undefined && a !== undefined && landingOf(b.targets) === landingOf(a.targets))
10
11
  continue;
11
12
  changed.push(...(b?.reads ?? []), ...(a?.reads ?? []));
12
13
  }
13
14
  return changed.filter((r) => r.target !== undefined);
14
15
  }
16
+ export function readLandingsOf(e) {
17
+ return [...targetsByCandidate(e)].map(([key, l]) => ({ key: digest(key), landing: landingOf(l.targets), raw: l.reads[0]?.candidate.raw ?? "", targets: [...l.targets].sort() }));
18
+ }
19
+ function landingOf(targets) {
20
+ return digest(JSON.stringify([...targets].sort()));
21
+ }
22
+ const digest = (s) => createHash("sha256").update(s, "utf8").digest("hex");
15
23
  function targetsByCandidate(e) {
16
24
  const out = new Map();
17
25
  const walk = (p, at) => {
@@ -35,9 +43,6 @@ function targetsByCandidate(e) {
35
43
  walk(e, "");
36
44
  return out;
37
45
  }
38
- function sameTargets(a, b) {
39
- return a.size === b.size && [...a].every((t) => b.has(t));
40
- }
41
46
  export async function withHomeViews(door, placed, established, signal) {
42
47
  const views = homeViewsOf(door.fs, placed, signal);
43
48
  const unread = [];
package/dist/tools/web.js CHANGED
@@ -3,6 +3,7 @@ import { defineTool, errorResult } from "../core/tools.js";
3
3
  import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
4
4
  import { discloseReminderShaped, observeReminderMarkEcho } from "../core/reminder-disclosure.js";
5
5
  import { egressThrownText } from "../core/untrusted-egress.js";
6
+ import { remoteWords } from "../core/protocol-http-auth.js";
6
7
  import { fetchFollowing, hopRefusal, HOST_FLOOR_REFUSED_CODE } from "../core/host-floor.js";
7
8
  import { hostOfUrl } from "../core/host-target.js";
8
9
  import { assertNoRetiredKeys } from "../core/retired-keys.js";
@@ -210,12 +211,13 @@ function isPermittedRedirectHop(from, to) {
210
211
  const there = hostOfUrl(to.toString());
211
212
  return "host" in here && "host" in there && here.host === there.host;
212
213
  }
214
+ const reasonPhrase = (res) => remoteWords(res.statusText, []);
213
215
  async function fetchUnderFloor(doFetch, start, floor, userAgent, signal, onResponse) {
214
216
  const out = await fetchFollowing(doFetch, start, { method: "GET", headers: { Accept: WEBFETCH_ACCEPT, "User-Agent": userAgent }, ...(signal !== undefined ? { signal } : {}) }, {
215
217
  refusal: (url) => hopRefusal(url, floor, true),
216
218
  follows: isPermittedRedirectHop,
217
219
  maxRedirects: MAX_REDIRECTS,
218
- onResponse: (res) => onResponse?.(res.status, res.statusText),
220
+ onResponse: (res) => onResponse?.(res.status, reasonPhrase(res)),
219
221
  });
220
222
  switch (out.kind) {
221
223
  case "answered":
@@ -328,7 +330,7 @@ export function webFetchToolSpec(config = {}) {
328
330
  return failure(`Error (WebFetch): ${fr.error}`, connectedMeta ? { code: connectedMeta.status, codeText: connectedMeta.statusText } : {});
329
331
  }
330
332
  res = fr.res;
331
- connectedMeta = { status: res.status, statusText: res.statusText };
333
+ connectedMeta = { status: res.status, statusText: reasonPhrase(res) };
332
334
  finalResponse = true;
333
335
  const stream = res.body;
334
336
  if (stream && typeof stream.getReader === "function") {
@@ -449,7 +451,7 @@ export function webFetchToolSpec(config = {}) {
449
451
  : {}),
450
452
  };
451
453
  if (!res.ok) {
452
- const headline = `Error (WebFetch): HTTP ${res.status} ${res.statusText} for ${parsed.toString()}`;
454
+ const headline = `Error (WebFetch): HTTP ${res.status} ${reasonPhrase(res)} for ${parsed.toString()}`;
453
455
  const looksBinary = bodyBytes !== undefined && sniffBinarySignature(bodyBytes) !== undefined;
454
456
  const errText = looksBinary
455
457
  ? ""
@@ -476,7 +478,7 @@ export function webFetchToolSpec(config = {}) {
476
478
  type: "web-fetch",
477
479
  url: parsed.toString(),
478
480
  code: res.status,
479
- codeText: res.statusText,
481
+ codeText: reasonPhrase(res),
480
482
  bytes: bodyBytes ? bodyBytes.length : raw.length,
481
483
  durationMs: Date.now() - startedAt,
482
484
  ...transferStateDetails,
@@ -508,7 +510,7 @@ export function webFetchToolSpec(config = {}) {
508
510
  type: "web-fetch",
509
511
  url: parsed.toString(),
510
512
  code: res.status,
511
- codeText: res.statusText,
513
+ codeText: reasonPhrase(res),
512
514
  bytes: bodyBytes.length,
513
515
  durationMs: Date.now() - startedAt,
514
516
  ...transferStateDetails,
@@ -634,7 +636,7 @@ export function webFetchToolSpec(config = {}) {
634
636
  type: "web-fetch",
635
637
  url: parsed.toString(),
636
638
  code: res.status,
637
- codeText: res.statusText,
639
+ codeText: reasonPhrase(res),
638
640
  bytes: bodyBytes ? bodyBytes.length : raw.length,
639
641
  result: out.length > RESULT_PREVIEW_CHARS ? `${out.slice(0, RESULT_PREVIEW_CHARS)}\n…[${out.length - RESULT_PREVIEW_CHARS} chars truncated — full text in the tool output]` : out,
640
642
  durationMs: Date.now() - startedAt,
@@ -990,7 +992,7 @@ export function createSearxngSearchBackend(baseUrl, options = {}) {
990
992
  throw new Error(`SearXNG was redirected more than ${SEARXNG_MAX_REDIRECTS} times`);
991
993
  const res = followed.res;
992
994
  if (!res.ok)
993
- throw new Error(`SearXNG ${res.status} ${res.statusText} from ${url.origin}${url.pathname}`);
995
+ throw new Error(`SearXNG ${res.status} ${reasonPhrase(res)} from ${url.origin}${url.pathname}`);
994
996
  const body = (await res.json());
995
997
  if (!Array.isArray(body.results))
996
998
  throw new Error(`SearXNG returned no results array from ${url.origin}${url.pathname} — is format=json enabled on this instance?`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.56.0",
3
+ "version": "7.57.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",