@sema-agent/core 7.53.0 → 7.54.1

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 (249) hide show
  1. package/CHANGELOG.md +291 -1
  2. package/LICENSE +3 -1
  3. package/dist/agents/peer-session-drain.d.ts +0 -1
  4. package/dist/agents/peer-session-drain.js +0 -2
  5. package/dist/agents/send-message-tool.js +3 -4
  6. package/dist/agents/subagent.d.ts +3 -5
  7. package/dist/agents/subagent.js +18 -11
  8. package/dist/agents/tool-filter.d.ts +7 -4
  9. package/dist/agents/tool-filter.js +7 -3
  10. package/dist/core/a2a.js +4 -2
  11. package/dist/core/allow-entry.d.ts +1 -0
  12. package/dist/core/allow-entry.js +1 -0
  13. package/dist/core/approval-object.d.ts +87 -0
  14. package/dist/core/approval-object.js +85 -0
  15. package/dist/core/arg-summary.d.ts +3 -2
  16. package/dist/core/arg-summary.js +17 -11
  17. package/dist/core/ask-origin.d.ts +43 -27
  18. package/dist/core/ask-origin.js +20 -11
  19. package/dist/core/ask-question.d.ts +0 -1
  20. package/dist/core/ask-question.js +4 -67
  21. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  22. package/dist/core/auto-compaction.d.ts +49 -50
  23. package/dist/core/auto-compaction.js +41 -51
  24. package/dist/core/auto-mode-prompt.d.ts +4 -2
  25. package/dist/core/auto-mode-prompt.js +4 -3
  26. package/dist/core/canonical-json.d.ts +69 -0
  27. package/dist/core/canonical-json.js +130 -8
  28. package/dist/core/checkpoint-requirements.d.ts +13 -7
  29. package/dist/core/checkpoint-requirements.js +4 -1
  30. package/dist/core/checkpoint-store.d.ts +33 -18
  31. package/dist/core/checkpoint-store.js +5 -4
  32. package/dist/core/child-clamp-seats.d.ts +45 -6
  33. package/dist/core/child-clamp-seats.js +56 -4
  34. package/dist/core/consolidate-scope.d.ts +1 -1
  35. package/dist/core/cross-boundary-taint.d.ts +213 -0
  36. package/dist/core/cross-boundary-taint.js +196 -0
  37. package/dist/core/decision-receipt.d.ts +44 -1
  38. package/dist/core/decision-receipt.js +16 -0
  39. package/dist/core/effective-path-target.d.ts +3 -0
  40. package/dist/core/effective-path-target.js +4 -0
  41. package/dist/core/engine-notice.d.ts +34 -2
  42. package/dist/core/engine-notice.js +17 -1
  43. package/dist/core/engine-storage-backend.d.ts +73 -0
  44. package/dist/core/engine-storage-backend.js +1 -0
  45. package/dist/core/file-history-store.d.ts +6 -2
  46. package/dist/core/file-history-store.js +1 -0
  47. package/dist/core/gate-fold.js +10 -2
  48. package/dist/core/gate-lanes.js +30 -21
  49. package/dist/core/gate-outcome.d.ts +7 -3
  50. package/dist/core/governance-codes.d.ts +7 -2
  51. package/dist/core/governance-codes.js +6 -0
  52. package/dist/core/hooks.d.ts +14 -14
  53. package/dist/core/hooks.js +1 -2
  54. package/dist/core/locked-config.d.ts +28 -1
  55. package/dist/core/locked-config.js +30 -3
  56. package/dist/core/lsp.d.ts +1 -1
  57. package/dist/core/mcp-server-spec.d.ts +66 -9
  58. package/dist/core/mcp-server-spec.js +52 -19
  59. package/dist/core/mcp.js +5 -4
  60. package/dist/core/memory-engine/engine.js +2 -1
  61. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  62. package/dist/core/memory-engine/export-bundle.js +1 -1
  63. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  64. package/dist/core/memory-engine/file-backend.js +1 -12
  65. package/dist/core/memory.d.ts +3 -3
  66. package/dist/core/mode-policy.d.ts +13 -8
  67. package/dist/core/mode-policy.js +3 -2
  68. package/dist/core/mods/kernel/receive.d.ts +2 -1
  69. package/dist/core/mods/kernel/receive.js +7 -2
  70. package/dist/core/parent-spec-seat.d.ts +10 -13
  71. package/dist/core/parent-spec-seat.js +14 -6
  72. package/dist/core/pause-registry.d.ts +35 -1
  73. package/dist/core/pause-registry.js +10 -0
  74. package/dist/core/permission-rule-consent.d.ts +18 -5
  75. package/dist/core/permission-rule-consent.js +13 -8
  76. package/dist/core/permission-rule-model.d.ts +29 -6
  77. package/dist/core/permission-rule-model.js +6 -1
  78. package/dist/core/permission-rule-org.d.ts +37 -0
  79. package/dist/core/permission-rule-org.js +34 -42
  80. package/dist/core/permission-rule-session.js +2 -2
  81. package/dist/core/permission-rule-store.d.ts +8 -0
  82. package/dist/core/permission-rule-store.js +1 -0
  83. package/dist/core/permission-rule-sync.js +3 -2
  84. package/dist/core/read-only-shell.d.ts +1 -1
  85. package/dist/core/read-only-shell.js +30 -9
  86. package/dist/core/remote-env.d.ts +41 -1
  87. package/dist/core/remote-env.js +18 -0
  88. package/dist/core/resume-startup-cause.d.ts +18 -0
  89. package/dist/core/resume-startup-cause.js +12 -0
  90. package/dist/core/runner/approval-feedback.d.ts +46 -0
  91. package/dist/core/runner/approval-feedback.js +41 -0
  92. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  93. package/dist/core/runner/compaction-call-options.js +2 -2
  94. package/dist/core/runner/contracts.d.ts +33 -41
  95. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  96. package/dist/core/runner/cross-boundary-lane.js +190 -0
  97. package/dist/core/runner/decide-continuation.d.ts +4 -4
  98. package/dist/core/runner/decide-continuation.js +3 -1
  99. package/dist/core/runner/gate-exit.d.ts +32 -12
  100. package/dist/core/runner/gate-exit.js +75 -33
  101. package/dist/core/runner/inherited-ask-grants.js +2 -2
  102. package/dist/core/runner/person-path-rows.d.ts +2 -2
  103. package/dist/core/runner/prepare-acquire-reconcile.d.ts +7 -9
  104. package/dist/core/runner/prepare-acquire-reconcile.js +8 -6
  105. package/dist/core/runner/prepare-ask-lane.js +3 -3
  106. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  107. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  108. package/dist/core/runner/prepare-config-doors.d.ts +12 -15
  109. package/dist/core/runner/prepare-config-doors.js +8 -3
  110. package/dist/core/runner/prepare-context-lane.js +6 -3
  111. package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
  112. package/dist/core/runner/prepare-delegation-surface.js +2 -3
  113. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  114. package/dist/core/runner/prepare-execution-env.js +2 -1
  115. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  116. package/dist/core/runner/prepare-gate-stations.js +4 -3
  117. package/dist/core/runner/prepare-hands-readface.d.ts +2 -2
  118. package/dist/core/runner/prepare-hands-readface.js +1 -1
  119. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  120. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  121. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  122. package/dist/core/runner/prepare-park-ask.js +9 -3
  123. package/dist/core/runner/prepare-policy-chain.js +2 -7
  124. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  125. package/dist/core/runner/prepare-safety-scan.js +34 -27
  126. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  127. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  128. package/dist/core/runner/prepare-task.js +13 -10
  129. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  130. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  131. package/dist/core/runner/prepare-wiring-manifest.d.ts +2 -2
  132. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  133. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  134. package/dist/core/runner/resume-admission.js +9 -9
  135. package/dist/core/runner/resume-apply.js +4 -4
  136. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  137. package/dist/core/runner/resume-policy-outcome.js +18 -15
  138. package/dist/core/runner/resume-preflight.js +2 -2
  139. package/dist/core/runner/resume-review-outcome.js +3 -3
  140. package/dist/core/runner/run-leg.js +57 -33
  141. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  142. package/dist/core/runner/run-terminal-adoption.js +15 -4
  143. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  144. package/dist/core/runner/session-file-state-replay.js +3 -2
  145. package/dist/core/runner/stream-steer-verb.js +2 -2
  146. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  147. package/dist/core/runner/tool-face-overlay.js +24 -15
  148. package/dist/core/runner-deps.d.ts +9 -9
  149. package/dist/core/session-store.d.ts +35 -16
  150. package/dist/core/session-store.js +20 -12
  151. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  152. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  153. package/dist/core/shell-wrapper-table.d.ts +5 -3
  154. package/dist/core/shell-wrapper-table.js +1 -1
  155. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  156. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  157. package/dist/core/store-contracts/session-store-contract.js +683 -0
  158. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  159. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  160. package/dist/core/task-event.d.ts +6 -4
  161. package/dist/core/task-registry-agent.js +1 -0
  162. package/dist/core/task-registry-shared.d.ts +5 -0
  163. package/dist/core/task-registry.d.ts +7 -0
  164. package/dist/core/task-registry.js +17 -0
  165. package/dist/core/task-result.d.ts +1 -1
  166. package/dist/core/task-spec.d.ts +6 -6
  167. package/dist/core/tighten-task-spec.d.ts +6 -4
  168. package/dist/core/tighten-task-spec.js +2 -2
  169. package/dist/core/tool-catalog-entries.d.ts +1 -1
  170. package/dist/core/tool-catalog-entries.js +52 -52
  171. package/dist/core/tool-face-reader.d.ts +49 -0
  172. package/dist/core/tool-face-reader.js +94 -0
  173. package/dist/core/tool-face.d.ts +17 -8
  174. package/dist/core/tool-face.js +1 -0
  175. package/dist/core/tool-policy.d.ts +64 -23
  176. package/dist/core/tool-policy.js +62 -56
  177. package/dist/core/tool-roster.d.ts +46 -28
  178. package/dist/core/tool-roster.js +91 -14
  179. package/dist/core/tool-spec-members.js +2 -2
  180. package/dist/core/tool-spec.d.ts +20 -4
  181. package/dist/core/tools.js +2 -0
  182. package/dist/core/trace.d.ts +24 -2
  183. package/dist/core/types.d.ts +2 -2
  184. package/dist/core/types.js +1 -1
  185. package/dist/core/untrusted-text.d.ts +7 -0
  186. package/dist/core/untrusted-text.js +3 -8
  187. package/dist/core/user-attachment.d.ts +43 -0
  188. package/dist/core/user-attachment.js +92 -15
  189. package/dist/engine/compaction/compaction.d.ts +2 -2
  190. package/dist/engine/compaction/utils.d.ts +2 -2
  191. package/dist/engine/harness/types.d.ts +7 -4
  192. package/dist/engine/llm/types.d.ts +7 -2
  193. package/dist/engine/llm/types.js +2 -1
  194. package/dist/engine/session/parent-chain-guard.d.ts +10 -0
  195. package/dist/engine/session/parent-chain-guard.js +10 -0
  196. package/dist/engine/session/repo-utils.d.ts +5 -0
  197. package/dist/engine/session/repo-utils.js +9 -0
  198. package/dist/engine/session/storage-base.js +5 -2
  199. package/dist/index.d.ts +23 -9
  200. package/dist/index.js +19 -8
  201. package/dist/internal/harness.d.ts +1 -0
  202. package/dist/internal/harness.js +1 -0
  203. package/dist/internal/llm.d.ts +1 -1
  204. package/dist/internal/llm.js +1 -1
  205. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  206. package/dist/orchestration/run-workflow-tool.js +1 -0
  207. package/dist/orchestration/workflow-governance.d.ts +1 -1
  208. package/dist/orchestration/workflow.d.ts +3 -0
  209. package/dist/orchestration/workflow.js +2 -1
  210. package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
  211. package/dist/prompt-assembly/catalog/schema.js +3 -0
  212. package/dist/prompt-assembly/overlay.js +2 -2
  213. package/dist/prompt-assembly/select.js +2 -2
  214. package/dist/prompts/default.d.ts +1 -1
  215. package/dist/shell/lex.d.ts +87 -8
  216. package/dist/shell/lex.js +283 -49
  217. package/dist/stores/file/index.d.ts +8 -6
  218. package/dist/tools/artifact/artifact-tool.js +10 -0
  219. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  220. package/dist/tools/fs/bash-program-position.js +3 -10
  221. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  222. package/dist/tools/fs/fs-bash.d.ts +3 -3
  223. package/dist/tools/fs/fs-bash.js +72 -101
  224. package/dist/tools/fs/fs-read.d.ts +12 -0
  225. package/dist/tools/fs/fs-read.js +10 -9
  226. package/dist/tools/fs/fs-shared.d.ts +27 -31
  227. package/dist/tools/fs/fs-shared.js +12 -15
  228. package/dist/tools/fs/fs-write.js +1 -1
  229. package/dist/tools/fs/index.js +2 -2
  230. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  231. package/dist/tools/fs/read-deny-home.js +5 -4
  232. package/dist/tools/fs/read-deny.d.ts +16 -3
  233. package/dist/tools/fs/read-deny.js +12 -9
  234. package/dist/tools/fs/safety.d.ts +7 -6
  235. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  236. package/dist/tools/fs/shell-read-bindings.js +15 -16
  237. package/dist/tools/fs/shell-read-device.js +1 -1
  238. package/dist/tools/fs/shell-read-plan.js +36 -47
  239. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  240. package/dist/tools/fs/shell-read-word.js +2 -20
  241. package/dist/tools/fs/shell-reentry-text.js +1 -1
  242. package/dist/tools/web.d.ts +2 -2
  243. package/package.json +4 -1
  244. package/test/export-surface.snapshot.json +5654 -5520
  245. package/wire-changes/core@7.53.0.json +30 -8
  246. package/wire-changes/core@7.54.0.json +2870 -0
  247. package/wire-changes/core@7.54.1.json +37 -0
  248. package/dist/core/shell-scan.d.ts +0 -60
  249. package/dist/core/shell-scan.js +0 -183
@@ -456,9 +456,22 @@ export declare function matchSegmentPatterns(path: string, compiled: readonly Co
456
456
  /** One entry's refusal under the deny list's entry law ({@link normalizeEntry}, the compiler's own), or `undefined` when the
457
457
  * entry is lawful — the law read without compiling, for a door that judges a list before anything folds it. */
458
458
  export declare function readDenyEntryProblem(entry: unknown, layer: string): string | undefined;
459
- /** Is a canonical pattern ROOTED — placed at the filesystem root ({@link ReadDenyEntry})? Only {@link canonicalReadDenyPattern}
460
- * puts a leading `/` on a canonical pattern, so the text is the whole answer. */
461
- export declare function isRootedReadDenyPattern(canonical: string): boolean;
459
+ /** Where a deny row is anchored ({@link readDenyRowAnchor}): the filesystem root, the execution environment's home, or nowhere. */
460
+ export type ReadDenyRowAnchorWord = "root" | "home" | "none";
461
+ /**
462
+ * WHERE a deny row is anchored, as the one word every reader of a row asks: `home` — a HOME-anchored row (its first segment is
463
+ * exactly {@link READ_DENY_HOME_WORD}): its run starts right below the execution environment's home; `root` — a ROOTED row (it
464
+ * opens with `/` and is not home-anchored): its run starts at the first segment of an absolute path; `none` — an unanchored row,
465
+ * which may match anywhere in a judged path. The compiler's placement, the linked-location pass, the band's "has anything to
466
+ * place" test, the head a mount resolves for a row and the canonical spelling itself all read the anchor here, so a row has one
467
+ * anchor however many faces ask.
468
+ *
469
+ * The pattern is read as given and gives the answer its canonical form gives, so a spelling and its canonical form are one row
470
+ * (`//etc` and `/etc` are both `root`; `/~/x` is `~/x`, so `home`: a leading `/` before the home word is no root). Whether the
471
+ * text is a LEGAL row (no backslash, at least one segment below its anchor) is not asked here: that is
472
+ * {@link persistedReadDenyEntryProblem} and the compiler's refusal.
473
+ */
474
+ export declare function readDenyRowAnchor(pattern: string): ReadDenyRowAnchorWord;
462
475
  /**
463
476
  * the PURE validation/canonicality predicate over a PERSISTED deny entry (checkpoint v9
464
477
  * face section), for the resume pre-CAS ladder: it must refuse BEFORE the approval CAS everything
@@ -320,7 +320,7 @@ export function compileSegmentPattern(raw, fold) {
320
320
  return null;
321
321
  const flags = fold === "unicode" ? "i" : "";
322
322
  const segments = parts.map((segment) => {
323
- const escaped = segment.replace(/[.+^${}()|[\]\\?]/g, "\\$&").replace(/\*/g, "[^/]*");
323
+ const escaped = segment.replace(/[.+^${}()|[\]\\?]/g, "\\$&").replace(/\*+/g, "[^/]*");
324
324
  return new RegExp(`^${escaped}$`, flags);
325
325
  });
326
326
  return { raw, segments, foldAscii: fold === "ascii" };
@@ -486,10 +486,12 @@ function normalizeEntry(entry, layer) {
486
486
  }
487
487
  function canonicalReadDenyPattern(pattern) {
488
488
  const joined = pattern.split("/").filter(Boolean).join("/");
489
- return pattern.startsWith("/") && !readDenyPatternRun(joined).homeAnchored ? `/${joined}` : joined;
489
+ return readDenyRowAnchor(pattern) === "root" ? `/${joined}` : joined;
490
490
  }
491
- export function isRootedReadDenyPattern(canonical) {
492
- return canonical.startsWith("/");
491
+ export function readDenyRowAnchor(pattern) {
492
+ if (readDenyPatternRun(pattern).homeAnchored)
493
+ return "home";
494
+ return pattern.startsWith("/") ? "root" : "none";
493
495
  }
494
496
  export function persistedReadDenyEntryProblem(e) {
495
497
  if (typeof e !== "object" || e === null)
@@ -548,14 +550,15 @@ export function compileReadDeny(additions = [], layer = "additions", builtin, ru
548
550
  }
549
551
  const homeSegments = [...new Set(checkedReadDenyHomes(homes))].map(homeAnchorSegments);
550
552
  const compiled = normalized.map((e) => {
551
- const { homeAnchored, run } = readDenyPatternRun(e.pattern);
553
+ const { run } = readDenyPatternRun(e.pattern);
554
+ const rowAnchor = readDenyRowAnchor(e.pattern);
552
555
  const fold = e.caseSensitive ? "none" : "ascii";
553
556
  const c = compileSegmentPattern(run, fold);
554
557
  if (c === null)
555
558
  throw new Error(`readDenyPatterns: pattern ${JSON.stringify(e.pattern)} compiled to zero segments.`);
556
- if (isRootedReadDenyPattern(e.pattern))
559
+ if (rowAnchor === "root")
557
560
  return { ...c, raw: e.pattern, anchors: [[]], rootedOnly: true };
558
- if (!homeAnchored)
561
+ if (rowAnchor === "none")
559
562
  return { ...c, raw: e.pattern };
560
563
  if (homeSegments.length === 0)
561
564
  return { ...c, raw: e.pattern, unplacedHome: true };
@@ -566,8 +569,8 @@ export function compileReadDeny(additions = [], layer = "additions", builtin, ru
566
569
  const linkedPlacements = [];
567
570
  normalized.forEach((e, i) => {
568
571
  const locations = linked.get(e.pattern);
569
- const { homeAnchored, run } = readDenyPatternRun(e.pattern);
570
- if (locations === undefined || !(homeAnchored || isRootedReadDenyPattern(e.pattern)))
572
+ const { run } = readDenyPatternRun(e.pattern);
573
+ if (locations === undefined || readDenyRowAnchor(e.pattern) === "none")
571
574
  return;
572
575
  const c = compiled[i];
573
576
  const rest = c.segments.slice(readDenyRunHead(run));
@@ -66,12 +66,13 @@ export interface ReadEntry {
66
66
  * disk (hash mismatch) always serves real content and the dedup disarms permanently.
67
67
  * Optional/additive: absent = normal entry. */
68
68
  seededFromContext?: boolean;
69
- /** The id of the tool call whose RESULT carried the body this entry dedups against: when the
70
- * request-layer stale-result pass clears THAT transmission, the entry retires with it
71
- * (`applyContextShrinkToReadFileState`, `tool-results-cleared` arm). Recorded only by the Read
72
- * lanes that TRANSMIT a body — a write-back's content is the model's own arguments and a
73
- * seeded/re-attached entry never rode a tool result, so neither carries this and neither is
74
- * retired by a clear. Optional/additive: absent (an older checkpoint) never matches a cleared id. */
69
+ /** The transmission that carried the body this entry dedups against — the tool call whose RESULT carried it, or the
70
+ * attachment frame that did (`attachmentTransmissionId`): when THAT transmission leaves the request (a stale-result
71
+ * clear, a trim), the entry retires with it (`applyContextShrinkToReadFileState`, `transmissions-gone` arm). Recorded
72
+ * only by the deliveries that TRANSMIT a body (a Read result or a frame replayed from the transcript included) — a
73
+ * write-back's content is the model's own arguments and a context-seeded entry never rode a message, so neither
74
+ * carries this and neither is retired by it. Optional/additive: absent (an
75
+ * older checkpoint) never matches. */
75
76
  transmittedByToolCallId?: string;
76
77
  }
77
78
  /** Per-task record of which files have been read, keyed by canonical path: owned by prepare-task,
@@ -437,9 +437,11 @@ export declare function isArithmeticCommand(segment: LexedSegment): boolean;
437
437
  /** The `[[` operators whose operands the arithmetic evaluator reads (`[[ x -eq y ]]`; `[` / `test` compare integers
438
438
  * without evaluating them). */
439
439
  export declare const ARITHMETIC_TEST_OPERATORS: ReadonlySet<string>;
440
- /** The substitutions a parameter expansion's body hands the arithmetic evaluator: its subscript (`${a[…]}`) and the
441
- * offset and length of a substring expansion (`${x:off:len}` — the shell expands them, then evaluates them). */
442
- export declare function parameterBodySubstitutions(name: string, body: string, table: BindingTable | undefined): string[];
440
+ /** The substitutions a parameter expansion's body hands the arithmetic evaluator: its subscript (`${a[…]}`, `${#a[…]}`,
441
+ * `${!a[…]}`) and the offset and length of a substring expansion (`${x:off:len}` — the shell expands them, then
442
+ * evaluates them), as the lexer's one reading of the body finds them ({@link parameterArithmeticTexts}). The body's own
443
+ * text only: what a VALUE runs when the expansion re-reads it is not followed (the station asks, see its caller). */
444
+ export declare function parameterBodySubstitutions(body: string, table: BindingTable | undefined): string[];
443
445
  /**
444
446
  * The substitutions the shell's ARITHMETIC evaluator runs while it evaluates `text`: every `$(…)`, backquote or process
445
447
  * substitution inside an array SUBSCRIPT of it (`a[$(…)]` — quoted or not: the evaluator re-reads the text it is
@@ -1,4 +1,4 @@
1
- import { argvOf, lexShell, literalTildeSpelling, substitutionsInHeredocBody } from "../../shell/lex.js";
1
+ import { argvOf, arithmeticTextIsPlain, expansionHidesRun, expansionsInHeredocBody, lexShell, literalTildeSpelling, namesRedirectDescriptor, parameterArithmeticTexts } from "../../shell/lex.js";
2
2
  import { shellWordsOf } from "../../shell/shell-words.js";
3
3
  import { isShellSyntaxWord, LOOP_LIST_HEADS, readProgramPosition } from "./bash-program-position.js";
4
4
  import { declaredAttributes, programRow, readArgRoles } from "./program-read-model.js";
@@ -456,18 +456,16 @@ export function bindingEffectOf(segment, argv, shape, readValue, shadow, availab
456
456
  for (const t of segment.tokens) {
457
457
  if (t.kind !== "heredoc" || t.quoted)
458
458
  continue;
459
- if (t.body.includes("${"))
459
+ const expansions = expansionsInHeredocBody(t.body);
460
+ const evaluatesUnseen = (x) => (x.expansion.kind === "parameter" && expansionHidesRun(x.expansion)) || (x.expansion.kind === "arithmetic" && !arithmeticTextIsPlain(x.expansion.body ?? ""));
461
+ if (expansions === undefined || expansions.some((x) => x.unresolved !== undefined || evaluatesUnseen(x)))
460
462
  unknownAll();
461
- for (const s of substitutionsInHeredocBody(t.body) ?? [])
462
- if (s.kind === "arithmetic")
463
- for (const name of arithmeticAssignedNames(s.body))
464
- kill(name);
463
+ for (const name of assignedByPieces((expansions ?? []).map((x) => ({ span: [0, 0], quotedBy: undefined, expansion: x.expansion }))))
464
+ kill(name);
465
465
  }
466
466
  segment.tokens.forEach((t, i) => {
467
- const next = segment.tokens[i + 1];
468
- const m = t.kind === "word" && next?.kind === "redirect" ? /^\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(t.raw) : null;
469
- if (m !== null)
470
- kill(m[1]);
467
+ if (t.kind === "word" && namesRedirectDescriptor(t, segment.tokens[i + 1]))
468
+ kill(t.raw.slice(1, -1));
471
469
  });
472
470
  const tokens = segment.tokens;
473
471
  if (isArithmeticCommand(segment)) {
@@ -665,8 +663,10 @@ function assignedByPieces(pieces) {
665
663
  const out = [];
666
664
  for (const p of pieces) {
667
665
  const e = p.expansion;
668
- if (e?.kind === "parameter" && e.name !== undefined && e.body !== undefined && new RegExp(`^${e.name}:?=`).test(e.body))
666
+ if (e?.kind === "parameter" && e.name !== undefined && e.body !== undefined && (e.body.startsWith(`${e.name}=`) || e.body.startsWith(`${e.name}:=`)))
669
667
  out.push(e.name);
668
+ if (e?.kind === "parameter" && e.body !== undefined)
669
+ out.push(...arithmeticAssignedNames(e.body));
670
670
  if (e?.kind === "arithmetic" && e.body !== undefined)
671
671
  out.push(...arithmeticAssignedNames(e.body));
672
672
  }
@@ -1042,12 +1042,11 @@ export function isArithmeticCommand(segment) {
1042
1042
  return a?.kind === "group" && a.op === "(" && b?.kind === "group" && b.op === "(";
1043
1043
  }
1044
1044
  export const ARITHMETIC_TEST_OPERATORS = new Set(["-eq", "-ne", "-lt", "-le", "-gt", "-ge"]);
1045
- export function parameterBodySubstitutions(name, body, table) {
1046
- const rest = body.slice(name.length);
1047
- const out = arithmeticSubstitutions(rest.startsWith("[") ? rest.slice(0, rest.indexOf("]") + 1 || undefined) : "", table);
1048
- const offset = /^(?:\[[^\]]*\])?:(?![-=+?])(.*)$/s.exec(rest)?.[1];
1045
+ export function parameterBodySubstitutions(body, table) {
1046
+ const { subscript, offset } = parameterArithmeticTexts(body);
1047
+ const out = arithmeticSubstitutions(subscript === undefined ? "" : `[${subscript}]`, table);
1049
1048
  if (offset !== undefined)
1050
- out.push(...substitutionBodies(`${offset}`, true), ...arithmeticSubstitutions(offset, table));
1049
+ out.push(...substitutionBodies(offset, true), ...arithmeticSubstitutions(offset, table));
1051
1050
  return [...new Set(out)];
1052
1051
  }
1053
1052
  export function arithmeticSubstitutions(text, table, chain = new Set(), budget = { left: ARITHMETIC_VALUES_MAX }) {
@@ -22,7 +22,7 @@ export function descriptorsBoundBy(r) {
22
22
  return [r.fd];
23
23
  if (r.op === "&>" || r.op === "&>>")
24
24
  return [1, 2];
25
- return [r.op === "<" || r.op === "<<<" || r.op === "<&" ? 0 : 1];
25
+ return [r.op === "<" || r.op === "<<<" || r.op === "<&" || r.op === "<>" ? 0 : 1];
26
26
  }
27
27
  export function bindDescriptors(into, r) {
28
28
  for (const fd of descriptorsBoundBy(r))
@@ -1,5 +1,5 @@
1
- import { lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTextOf, literalTildeSpelling, } from "../../shell/lex.js";
2
- import { programTextOf, readOfText, readsOf, rewritten, segmentReadingsOf, settledFieldsOf, spelledWithHome, isArithmeticBody, unreadableSentenceOf } from "./shell-read-word.js";
1
+ import { expansionsInHeredocBody, lexShell, PARSE_FAILURE_SENTENCE, substitutionsInHeredocBody, literalTextOf, literalTildeSpelling, parameterRereadsValue, UNRESOLVED_SENTENCE, } from "../../shell/lex.js";
2
+ import { programTextOf, readOfText, readsOf, rewritten, segmentReadingsOf, settledFieldsOf, spelledWithHome, unreadableSentenceOf } from "./shell-read-word.js";
3
3
  import { assignmentArithmetic, bare, listedVerbCandidates, pathSubcommandOf, programTextSentence, pushAssignment, roleReads, subscriptOnly, unlistedWords } from "./shell-read-argv.js";
4
4
  import { ARITHMETIC_TEST_OPERATORS, BindingThread, arithmeticSubstitutions, isArithmeticCommand, bindingEffectOf, joinRebound, loopListHeadAt, namesBoundIn, parameterBodySubstitutions, projectSegment, valueOf, } from "./shell-read-bindings.js";
5
5
  import { commandEnvironmentNames, isShellSyntaxWord, programBasename, programPositionRunsShell, readProgramPosition, shellScanDeclineOf, } from "./bash-program-position.js";
@@ -310,51 +310,40 @@ function segmentFactsRead(segment, p, decl, table, nestedPlan, fed, toks) {
310
310
  const nested = [];
311
311
  const elsewhere = new Set();
312
312
  const ctx = { table, reentered: [], programTextSeen: false };
313
- 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] : []));
313
+ const enclosedToo = (e) => [e, ...(e.inner ?? []).flatMap(enclosedToo)];
314
+ const expansionsOf = (sg) => sg.tokens.flatMap((t) => (t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined)?.pieces ?? []).flatMap((q) => (q.expansion !== undefined ? enclosedToo(q.expansion) : []));
314
315
  const count = (list, body) => list.filter((b) => b === body).length;
315
- const arithmeticBody = (body) => {
316
- const l = lexShell(body);
317
- if ("parseFailure" in l)
318
- nested.push(nestedPlan(body, "substitution"));
319
- else
320
- for (const s of l.segments.flatMap((sg) => sg.substitutions))
321
- nested.push(nestedPlan(s, "substitution"));
322
- ctx.reentered.push(...arithmeticSubstitutions(body, table));
323
- };
324
- for (const body of segment.substitutions) {
325
- if (count(arithmetic, body) > 0 && count(arithmetic, body) === count(segment.substitutions, body))
326
- arithmeticBody(body);
327
- else
328
- nested.push(nestedPlan(body, "substitution"));
329
- }
330
- let unreadableHeredoc = false;
331
- for (const t of segment.tokens) {
332
- if (t.kind !== "heredoc" || t.quoted)
333
- continue;
334
- const inner = substitutionsInHeredocBody(t.body);
335
- if (inner === undefined) {
336
- unreadableHeredoc = true;
337
- continue;
338
- }
339
- for (const s of inner) {
340
- if (s.kind === "arithmetic" && isArithmeticBody(s.body))
341
- arithmeticBody(s.body);
316
+ const substitute = (body, here) => void (here ? ctx.reentered.push(body) : nested.push(nestedPlan(body, "substitution")));
317
+ const unread = (sentence) => void nested.push({ segments: [], unreadable: sentence, shape: { grouped: false, backgrounded: false, strayRedirection: false } });
318
+ const parameter = (e) => void (e.kind === "parameter" && e.body !== undefined && (parameterRereadsValue(e.body) && unread("a parameter expansion re-reads a value (an indirect `${!name}`, an evaluating `${name@P}`), which this station does not follow"), ctx.reentered.push(...parameterBodySubstitutions(e.body, table))));
319
+ const expandedText = (all, undelimited) => {
320
+ if (all === undefined)
321
+ return void unread(undelimited), false;
322
+ for (const x of all) {
323
+ if (x.expansion.kind === "arithmetic")
324
+ arithmeticBody(x.expansion.body ?? "");
325
+ else if (x.expansion.kind === "command")
326
+ substitute(x.expansion.body ?? "", x.currentShell === true);
342
327
  else
343
- nested.push(nestedPlan(s.body, "substitution"));
328
+ parameter(x.expansion);
329
+ if (x.unresolved !== undefined)
330
+ void (unread(UNRESOLVED_SENTENCE[x.unresolved]), x.unresolved === "parameter_text" && expandedText(expansionsInHeredocBody(x.expansion.body ?? ""), UNRESOLVED_SENTENCE.parameter_text));
344
331
  }
345
- }
346
- if (unreadableHeredoc)
347
- nested.push({ segments: [], unreadable: "a here-document body carries a substitution this lexer cannot delimit", shape: { grouped: false, backgrounded: false, strayRedirection: false } });
332
+ return all.some((x) => x.expansion.kind === "command");
333
+ };
334
+ const arithmeticBody = (body) => void (expandedText(expansionsInHeredocBody(body), "an arithmetic expansion carries a substitution this lexer cannot delimit") && unread("an arithmetic expansion evaluates what a command substitution in it prints, which this station does not read"), ctx.reentered.push(...arithmeticSubstitutions(body, table)));
335
+ const arithmetic = expansionsOf(segment).flatMap((e) => (e.kind === "arithmetic" && e.body !== undefined ? [e.body] : []));
336
+ for (const body of segment.substitutions)
337
+ (count(arithmetic, body) > 0 && count(arithmetic, body) === count(segment.substitutions, body) ? arithmeticBody(body) : substitute(body, segment.currentShell?.includes(body) === true));
338
+ for (const t of segment.tokens)
339
+ if (t.kind === "heredoc" && !t.quoted)
340
+ expandedText(expansionsInHeredocBody(t.body), "a here-document body carries a substitution this lexer cannot delimit");
341
+ for (const e of expansionsOf(segment))
342
+ parameter(e);
348
343
  const shellSide = new Set(nested);
349
344
  if (isArithmeticCommand(segment))
350
345
  ctx.reentered.push(...arithmeticSubstitutions(p.argv.map((w) => w.folded).join(" "), table));
351
- for (const t of segment.tokens) {
352
- const w = t.kind === "word" ? t : t.kind === "redirect" ? t.target : undefined;
353
- for (const q of w?.pieces ?? [])
354
- if (q.expansion?.kind === "parameter" && q.expansion.name !== undefined && q.expansion.body !== undefined)
355
- ctx.reentered.push(...parameterBodySubstitutions(q.expansion.name, q.expansion.body, table));
356
- }
357
- const redirects = p.redirects.map((r) => redirectFact(r, table, decl.homeDir));
346
+ const redirects = p.redirects.flatMap((r) => redirectFacts(r, table, decl.homeDir));
358
347
  const reads = [];
359
348
  for (const r of redirects)
360
349
  if (r.direction === "in" && r.target !== undefined)
@@ -670,15 +659,15 @@ function dedupe(reads) {
670
659
  function relativeSpelling(spelled) {
671
660
  return !(isAbsoluteForFamily("own", spelled) || spelled.startsWith("~"));
672
661
  }
673
- function redirectFact(r, table, homeDir) {
662
+ function redirectFacts(r, table, homeDir) {
674
663
  const fd = r.fd !== undefined ? { fd: r.fd } : {};
675
664
  if (r.target === undefined)
676
- return { op: r.op, direction: "dup", ...fd };
677
- if (r.op === "<")
678
- return { op: r.op, direction: "in", ...fd, target: bare(readsOf([r.target], table, homeDir)[0], "stdin_redirect") };
665
+ return [{ op: r.op, direction: "dup", ...fd }];
679
666
  if (r.op === "<<<")
680
- return { op: r.op, direction: "in", ...fd };
681
- return { op: r.op, direction: "out", ...fd, target: bare(readsOf([r.target], table, homeDir)[0], "write_redirect_target") };
667
+ return [{ op: r.op, direction: "in", ...fd }];
668
+ const read = readsOf([r.target], table, homeDir)[0];
669
+ const inward = r.op === "<" || r.op === "<>" ? [{ op: r.op, direction: "in", ...fd, target: bare(read, "stdin_redirect") }] : [];
670
+ return r.op === "<" ? inward : [...inward, { op: r.op, direction: "out", ...fd, target: bare(read, "write_redirect_target") }];
682
671
  }
683
672
  function commandStringValue(row, toks, option) {
684
673
  const at = toks.findIndex((r) => r.folded === option);
@@ -111,13 +111,8 @@ export declare function spelledWithHome(spelled: string, raw: string, home: stri
111
111
  * bash expands the pattern (or reads another delimiter) where the lexer reads a split word, so what the text opens is
112
112
  * not what the plan names: the whole text asks, and a deny hit anywhere still refuses. `globs: false` = the text is an
113
113
  * arithmetic expression (`$((2*(3+4)))`), which bash evaluates and never globs. A compound assignment's value is no
114
- * such word: the assignment reads nothing. */
114
+ * such word — the assignment reads nothing — unless the unread value may run something ({@link compoundValueHidesRun}). */
115
115
  export declare function unreadableSentenceOf(lexed: LexedCommand, text: string, globs?: boolean): string | undefined;
116
- /** An `$((…))` body the shell reads as ARITHMETIC: one parenthesised group spanning the whole body (`(2*(3+4))`),
117
- * its bounds read off the lexer's structure — a quoted, escaped or commented parenthesis is text, not a bound — across
118
- * the segments the lexer cuts at `&&` `||` `&` `|` and line breaks (operators arithmetic shares with the shell).
119
- * `$((cat x); :)` opens the same way but its first group closes early: a command substitution holding a subshell. */
120
- export declare function isArithmeticBody(body: string): boolean;
121
116
  /** What the shell does to a word (or an assignment's value) before the program sees it, read off its pieces: the
122
117
  * lexer's expansion facts alone. A quoted text that an evaluator or an interpreter later RUNS (an arithmetic
123
118
  * subscript, an inline program) is no rewrite of the word: the slot it stands in says what it is (the program read
@@ -1,4 +1,4 @@
1
- import { argvOf, lexShell, literalTildeSpelling, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
1
+ import { argvOf, compoundValueHidesRun, lexShell, literalTildeSpelling, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
2
2
  import { expansionInWord, extglobOpenerIn, globInWord, homeExpansionOf } from "./bash-readonly-classifier.js";
3
3
  import { floorOf, judgedSpellingOf, namesIn, wordFloorValues, wordValues } from "./shell-read-bindings.js";
4
4
  export function readsOf(words, table, homeDir, split = false) {
@@ -150,7 +150,7 @@ export function unreadableSentenceOf(lexed, text, globs = true) {
150
150
  for (const segment of lexed.segments) {
151
151
  for (const token of segment.tokens) {
152
152
  const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
153
- if (word?.unresolved === "compound_array_value" && word.assignment?.form === "compound")
153
+ if (word?.unresolved === "compound_array_value" && word.assignment?.form === "compound" && !compoundValueHidesRun(word))
154
154
  continue;
155
155
  if (word?.unresolved !== undefined)
156
156
  return UNRESOLVED_SENTENCE[word.unresolved];
@@ -158,24 +158,6 @@ export function unreadableSentenceOf(lexed, text, globs = true) {
158
158
  }
159
159
  return globs && extglobOpenerIn(lexed, text) ? "an extended pattern (`?(…)` `*(…)` `+(…)` `@(…)` `!(…)`) is one this reader does not expand" : undefined;
160
160
  }
161
- export function isArithmeticBody(body) {
162
- const l = body.startsWith("(") && body.endsWith(")") ? lexShell(body) : undefined;
163
- if (l === undefined || "parseFailure" in l)
164
- return false;
165
- const tokens = l.segments.flatMap((sg) => sg.tokens);
166
- if (tokens[0]?.kind !== "group" || tokens[0].op !== "(")
167
- return false;
168
- let depth = 0;
169
- for (const [i, t] of tokens.entries()) {
170
- if (t.kind !== "group")
171
- continue;
172
- if (t.op === "(")
173
- depth++;
174
- else if (--depth === 0)
175
- return i === tokens.length - 1;
176
- }
177
- return false;
178
- }
179
161
  export function expandsOf(w) {
180
162
  if (expansionInWord(w))
181
163
  return "unresolvable";
@@ -210,7 +210,7 @@ export function stdinTextsOf(segment, fed) {
210
210
  const literal = t.quoted ? t.body : literalHeredocBody(t.body);
211
211
  out.push(literal === undefined ? { from: t.body } : { from: t.body, literal });
212
212
  }
213
- else if (t.kind === "redirect" && (t.fd ?? 0) === 0 && (t.op === "<<<" || t.op === "<" || (t.op === "<&" && t.dup !== "-"))) {
213
+ else if (t.kind === "redirect" && (t.fd ?? 0) === 0 && (t.op === "<<<" || t.op === "<" || t.op === "<>" || (t.op === "<&" && t.dup !== "-"))) {
214
214
  const literal = t.op === "<<<" && t.target !== undefined ? literalTextOf(t.target.pieces) : undefined;
215
215
  out.push({ from: `${t.op}${t.target?.raw ?? t.dup ?? ""}`, ...(literal !== undefined ? { literal } : {}) });
216
216
  replacesPipe = true;
@@ -77,7 +77,7 @@ export interface WebFetchGrounding {
77
77
  }
78
78
  export declare function htmlToText(html: string): string;
79
79
  export declare function webFetchToolSpec(config?: WebFetchConfig): ToolSpec;
80
- /** Defined (harness) form of web_fetch — back-compat for direct execution / tests. For `spec.tools`, use the raw
80
+ /** Defined (harness) form of web_fetch — back-compat (RL-1336-webfetch-factory) for direct execution / tests. For `spec.tools`, use the raw
81
81
  * {@link webFetchToolSpec} (prepare-task defineTool-wraps spec.tools entries).
82
82
  *
83
83
  * ⚠️ CTX-DEPENDENT BEHAVIOR: this factory takes no `enrichCtx`, so the product it returns is sealed
@@ -215,7 +215,7 @@ export interface WebSearchConfig {
215
215
  * `allowed_domains`/`blocked_domains` HINTS so a backend that supports native domain filtering (e.g. a
216
216
  * `site:` query) can apply them up front. A backend MAY ignore `opts` — core re-enforces the domain
217
217
  * constraint on the returned results as a floor (see `createWebSearchTool`), so honoring it is an
218
- * optimization, not a correctness requirement. Back-compat: existing 2-arg backends keep working.
218
+ * optimization, not a correctness requirement. A backend declaring fewer parameters is accepted as is.
219
219
  */
220
220
  search: (query: string, signal?: AbortSignal, opts?: {
221
221
  allowedDomains?: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.53.0",
3
+ "version": "7.54.1",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -77,6 +77,8 @@
77
77
  "gate:debts:rc": "node scripts/verify-debts.mjs --rc-gate",
78
78
  "gate:anchor-claims": "node scripts/verify-anchor-claims.mjs",
79
79
  "gate:anchor-claims:rc": "node scripts/verify-anchor-claims.mjs --require-corpus",
80
+ "gate:retirement-lines": "node scripts/verify-retirement-lines.mjs",
81
+ "gate:retirement-lines:rc": "node scripts/verify-retirement-lines.mjs --rc",
80
82
  "gate:rulings": "node scripts/rulings-gate.mjs --require-comms",
81
83
  "gate:error-surface": "node scripts/verify-error-surface.mjs",
82
84
  "gate:criteria": "node scripts/criteria-lint.mjs",
@@ -111,6 +113,7 @@
111
113
  "gate:negative-controls": "vitest run test/gate-negative-controls.test.ts",
112
114
  "gate:memory-ga": "node scripts/gate-memory-distillation-ga.mjs",
113
115
  "gate:all": "node scripts/gate-all.mjs",
116
+ "release:preflight": "node scripts/release-preflight.mjs",
114
117
  "car:gate": "node scripts/car-gate.mjs"
115
118
  },
116
119
  "dependencies": {