@sema-agent/core 7.53.0 → 7.54.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 (244) hide show
  1. package/CHANGELOG.md +277 -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 +3 -4
  104. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  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 +639 -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/repo-utils.d.ts +5 -0
  195. package/dist/engine/session/repo-utils.js +9 -0
  196. package/dist/engine/session/storage-base.js +2 -2
  197. package/dist/index.d.ts +23 -9
  198. package/dist/index.js +19 -8
  199. package/dist/internal/llm.d.ts +1 -1
  200. package/dist/internal/llm.js +1 -1
  201. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  202. package/dist/orchestration/run-workflow-tool.js +1 -0
  203. package/dist/orchestration/workflow-governance.d.ts +1 -1
  204. package/dist/orchestration/workflow.d.ts +3 -0
  205. package/dist/orchestration/workflow.js +2 -1
  206. package/dist/prompt-assembly/catalog/schema.d.ts +3 -0
  207. package/dist/prompt-assembly/catalog/schema.js +3 -0
  208. package/dist/prompt-assembly/overlay.js +2 -2
  209. package/dist/prompt-assembly/select.js +2 -2
  210. package/dist/prompts/default.d.ts +1 -1
  211. package/dist/shell/lex.d.ts +87 -8
  212. package/dist/shell/lex.js +283 -49
  213. package/dist/stores/file/index.d.ts +8 -6
  214. package/dist/tools/artifact/artifact-tool.js +10 -0
  215. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  216. package/dist/tools/fs/bash-program-position.js +3 -10
  217. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  218. package/dist/tools/fs/fs-bash.d.ts +3 -3
  219. package/dist/tools/fs/fs-bash.js +72 -101
  220. package/dist/tools/fs/fs-read.d.ts +12 -0
  221. package/dist/tools/fs/fs-read.js +10 -9
  222. package/dist/tools/fs/fs-shared.d.ts +27 -31
  223. package/dist/tools/fs/fs-shared.js +12 -15
  224. package/dist/tools/fs/fs-write.js +1 -1
  225. package/dist/tools/fs/index.js +2 -2
  226. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  227. package/dist/tools/fs/read-deny-home.js +5 -4
  228. package/dist/tools/fs/read-deny.d.ts +16 -3
  229. package/dist/tools/fs/read-deny.js +12 -9
  230. package/dist/tools/fs/safety.d.ts +7 -6
  231. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  232. package/dist/tools/fs/shell-read-bindings.js +15 -16
  233. package/dist/tools/fs/shell-read-device.js +1 -1
  234. package/dist/tools/fs/shell-read-plan.js +36 -47
  235. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  236. package/dist/tools/fs/shell-read-word.js +2 -20
  237. package/dist/tools/fs/shell-reentry-text.js +1 -1
  238. package/dist/tools/web.d.ts +2 -2
  239. package/package.json +4 -1
  240. package/test/export-surface.snapshot.json +5654 -5520
  241. package/wire-changes/core@7.53.0.json +30 -8
  242. package/wire-changes/core@7.54.0.json +2870 -0
  243. package/dist/core/shell-scan.d.ts +0 -60
  244. package/dist/core/shell-scan.js +0 -183
@@ -1,10 +1,10 @@
1
- import { readRowBehavior } from "./permission-rule-model.js";
1
+ import { readRowBehavior, RULE_ADD_ORIGINS } from "./permission-rule-model.js";
2
2
  import { isValidConsentScope, normalizePersistedRule } from "./permission-rule-store.js";
3
3
  function isValidOverlayAdd(add) {
4
4
  const a = add;
5
5
  if (typeof a?.dot?.actor !== "string" || a.dot.actor === "" || typeof a.dot.counter !== "number" || !Number.isFinite(a.dot.counter))
6
6
  return false;
7
- if (a.origin !== "user" && a.origin !== "imported-cc" && a.origin !== "starter")
7
+ if (!RULE_ADD_ORIGINS.some((word) => a.origin === word))
8
8
  return false;
9
9
  return typeof a.createdAt === "string";
10
10
  }
@@ -45,6 +45,7 @@ import type { PersistedRule, RuleAdd, RuleDot, RuleScope, RuleTombstone } from "
45
45
  import { type RuleBehavior } from "./permission-rule-model.js";
46
46
  import type { RuleSyncDropReason, RuleQuarantineReason } from "./governance-codes.js";
47
47
  import type { StoreDurability, StoreFidelity } from "./checkpoint-store.js";
48
+ import type { AssertAllKeysHandled } from "./ask-origin.js";
48
49
  /** One store read: the rules as persisted, their tombstones, and the revision they were read at. */
49
50
  export interface StoredRules {
50
51
  /** Rules with their FULL add sets — tombstoned dots already removed by the backend on read. */
@@ -92,6 +93,13 @@ export type RuleOwner = {
92
93
  } | {
93
94
  kind: "local-owner";
94
95
  };
96
+ /** The owner discriminants as a value: the `kind` words of {@link RuleOwner}, both directions fenced at compile time
97
+ * ({@link RuleOwnerKindsCoverEveryArm} / {@link RuleOwnerArmsCoverEveryKind}). */
98
+ export declare const RULE_OWNER_KINDS: readonly ["principal", "local-owner"];
99
+ /** Compile fence: an arm whose `kind` is not in {@link RULE_OWNER_KINDS} turns the argument into that word. */
100
+ export type RuleOwnerKindsCoverEveryArm = AssertAllKeysHandled<Exclude<RuleOwner["kind"], (typeof RULE_OWNER_KINDS)[number]>>;
101
+ /** Compile fence, the other direction: a word in {@link RULE_OWNER_KINDS} that no arm carries. */
102
+ export type RuleOwnerArmsCoverEveryKind = AssertAllKeysHandled<Exclude<(typeof RULE_OWNER_KINDS)[number], RuleOwner["kind"]>>;
95
103
  /** Do two owners name the same bucket? */
96
104
  export declare function sameRuleOwner(a: RuleOwner, b: RuleOwner): boolean;
97
105
  /** Resolves one owner's durable partition. `undefined` ⇒ a partition that reports zero rules. This is
@@ -3,6 +3,7 @@ import { canonicalize } from "./canonical-json.js";
3
3
  import { escapeForDisclosure, isRuleBehavior, isRuleLive, parseRuleText } from "./permission-rule-model.js";
4
4
  import { thrownText } from "./thrown-value.js";
5
5
  import { validatePath } from "./effective-path-target.js";
6
+ export const RULE_OWNER_KINDS = Object.freeze(["principal", "local-owner"]);
6
7
  export function sameRuleOwner(a, b) {
7
8
  return a.kind === "principal" ? b.kind === "principal" && a.principal === b.principal : b.kind === "local-owner";
8
9
  }
@@ -1,4 +1,4 @@
1
- import { escapeForDisclosure, isRuleBehavior, parseRuleText } from "./permission-rule-model.js";
1
+ import { escapeForDisclosure, isRuleBehavior, parseRuleText, RULE_ADD_ORIGINS } from "./permission-rule-model.js";
2
2
  import { applyTombstones, errText, isProjectScopeRoot, joinFrontiers, joinRuleStates, normalizePersistedRule, readRuleWriteOutcome, ruleSyncVector, sameRuleIdentity, screenRuleSyncState, writerOf, } from "./permission-rule-store.js";
3
3
  import { emitTrace } from "./trace.js";
4
4
  export const PERMISSION_RULE_SYNC_PATH = "/v1/permission-rules/sync";
@@ -273,6 +273,7 @@ function pickScopeCaptured(v) {
273
273
  function pickScope(v) {
274
274
  return pickScopeCaptured(v).scope;
275
275
  }
276
+ const isRuleAddOrigin = (v) => RULE_ADD_ORIGINS.some((word) => v === word);
276
277
  function pickAdd(v) {
277
278
  const a = v;
278
279
  const dot = pickDot(a?.dot);
@@ -280,7 +281,7 @@ function pickAdd(v) {
280
281
  const createdAt = a?.createdAt;
281
282
  if (dot === undefined)
282
283
  return undefined;
283
- if (origin !== "user" && origin !== "imported-cc" && origin !== "starter")
284
+ if (!isRuleAddOrigin(origin))
284
285
  return undefined;
285
286
  if (typeof createdAt !== "string")
286
287
  return undefined;
@@ -1,4 +1,4 @@
1
- import { type ShellCommandShape } from "./shell-lexer.js";
1
+ import { type ShellCommandShape } from "./shell-run-candidates.js";
2
2
  import { type LineDescriptors } from "../tools/fs/shell-read-device.js";
3
3
  /** The reader's answer: read-only, or not — with the first reason the reading stopped on (a sentence a
4
4
  * trace or a card can show; never adjudication input). */
@@ -1,5 +1,5 @@
1
1
  import { posix } from "node:path";
2
- import { readShellCommand } from "./shell-lexer.js";
2
+ import { lexedWordOf, readShellCommand } from "./shell-run-candidates.js";
3
3
  import { descriptorsOfPlan, newLineDescriptors, standardDeviceUnrebound } from "../tools/fs/shell-read-device.js";
4
4
  import { planShellReads } from "../tools/fs/shell-read-plan.js";
5
5
  import { writesOnlyStandardDevices } from "../tools/fs/shell-read-write-targets.js";
@@ -33,8 +33,7 @@ const TABLE_ROWS = Object.entries(READ_ONLY_COMMAND_TABLE)
33
33
  .map(([key, row]) => ({ words: key.split(" "), row }))
34
34
  .sort((a, b) => b.words.length - a.words.length);
35
35
  const READING_REDIRECTS = new Set(["<", "<<", "<<-", "<&", "<<<"]);
36
- const LITERAL_ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)\+?=(?:"[^"$`\\]*"|'[^']*'|[A-Za-z0-9_./:+-]*)$/;
37
- const ASSIGNMENT_NAME = /^([A-Za-z_][A-Za-z0-9_]*)(?:\[[^\]]*\])?\+?=/;
36
+ const BARE_VALUE_CHARACTERS = /^[A-Za-z0-9_./:+-]*$/;
38
37
  const FLAG_WORD = /^-[a-zA-Z0-9_-]/;
39
38
  const NUMBER_LITERAL = /^[-+]?(0[xX][0-9a-fA-F]+|[0-9]+#[0-9a-zA-Z]+|[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)$/;
40
39
  const PRINTF_LENGTH = "[lLhqjzZt]*";
@@ -90,20 +89,42 @@ export function readOnlyLineObjection(command, cwd) {
90
89
  return lineStructureObjection(shape) ?? (changesDirectoryBeforeGit(shape, cwd) ? CD_GIT_REASON : undefined);
91
90
  }
92
91
  function expansionKind(w) {
92
+ const lexed = lexedWordOf(w);
93
+ if (lexed !== undefined && (lexed.unresolved !== undefined || lexed.quotedSpans.some((s) => s.by === "$'")))
94
+ return "variable";
93
95
  if (w.expands === false)
94
96
  return "none";
95
- if (/[$`]|[<>]\(/.test(w.raw))
97
+ if (lexed === undefined)
98
+ return "variable";
99
+ const computed = (e) => VALUE_EXPANSIONS.has(e.kind) || e.inner?.some(computed) === true;
100
+ if (lexed.expansions.some((p) => p.expansion !== undefined && computed(p.expansion)))
96
101
  return "variable";
97
102
  if (w.expands === "many")
98
103
  return "glob";
99
104
  return "none";
100
105
  }
106
+ const VALUE_EXPANSIONS = new Set(["parameter", "command", "process", "arithmetic"]);
101
107
  function leadingAssignmentCount(argv) {
102
108
  let k = 0;
103
- while (k < argv.length && ASSIGNMENT_NAME.test(argv[k].raw))
109
+ while (k < argv.length && lexedWordOf(argv[k])?.assignment?.bare === true)
104
110
  k++;
105
111
  return k;
106
112
  }
113
+ function literalAssignmentValue(a) {
114
+ if (a.subscript !== undefined || a.form !== "scalar" || a.value.unresolved !== undefined || a.value.expansions.length > 0)
115
+ return false;
116
+ const [only, ...rest] = a.value.pieces;
117
+ if (only === undefined)
118
+ return true;
119
+ if (rest.length > 0 || only.literal === undefined)
120
+ return false;
121
+ if (only.quotedBy === undefined)
122
+ return BARE_VALUE_CHARACTERS.test(only.literal);
123
+ const oneRegion = a.value.raw.length === only.literal.length + 2;
124
+ if (only.quotedBy === "'")
125
+ return oneRegion;
126
+ return only.quotedBy === '"' && oneRegion && !/[$`\\]/.test(only.literal);
127
+ }
107
128
  function peelBuiltinPrefix(words) {
108
129
  let t = words;
109
130
  for (;;) {
@@ -142,11 +163,11 @@ function segmentVerdict(seg, line, bindings) {
142
163
  }
143
164
  const k = leadingAssignmentCount(seg.argv);
144
165
  for (const w of seg.argv.slice(0, k)) {
145
- const m = LITERAL_ASSIGNMENT.exec(w.raw);
146
- if (m === null)
166
+ const a = lexedWordOf(w).assignment;
167
+ if (!literalAssignmentValue(a))
147
168
  return NOT(`the assignment \`${w.raw}\` is not a literal value`);
148
- if (!ENV_NAMES.has(m[1]))
149
- return NOT(`the environment variable \`${m[1]}\` can alter what a program does`);
169
+ if (!ENV_NAMES.has(a.name))
170
+ return NOT(`the environment variable \`${a.name}\` can alter what a program does`);
150
171
  }
151
172
  const run = seg.argv.slice(k);
152
173
  if (run.length === 0)
@@ -66,6 +66,19 @@ export interface WorkspaceHandle {
66
66
  */
67
67
  deviceId?: string;
68
68
  }
69
+ /** The one list of what a {@link WorkspaceHandle} is: every member the interface declares. */
70
+ export declare const WORKSPACE_HANDLE_MEMBERS: ReadonlyArray<keyof WorkspaceHandle>;
71
+ /**
72
+ * A frozen copy of `handle` holding exactly the declared members it carries as strings: shallow-frozen, which is deep here
73
+ * because every member is a string (a decoration or a function member of the source never reaches the copy), and no
74
+ * structured-clone step to fail on an exotic value. Each member is read the way the engine's own handle comparison reads it.
75
+ *
76
+ * ONE copy per live handle: a second call for the same object answers the copy the first call made (the members are read
77
+ * once), so every place the engine hands a checkpointed handle to the outside — the resume preflight's facts, the env
78
+ * factory's {@link ExecutionEnvFactoryContext.resumeWorkspace}, the restore's {@link VmLifecycleOptions.priorHandle} —
79
+ * delivers the same object, not three renderings of one fact.
80
+ */
81
+ export declare function frozenHandleCopy(handle: WorkspaceHandle): Readonly<WorkspaceHandle>;
69
82
  /**
70
83
  * A reference to a secret injected at {@link RemoteExecutionEnv.connect}/{@link RemoteExecutionEnv.postResumeInit}
71
84
  * time (design/48 §5 gap 7) — never baked into the image. The control plane resolves {@link ref} to a value out of
@@ -114,8 +127,12 @@ export interface VmLifecycleOptions {
114
127
  * handle (never echo `priorHandle.mountPath` unhonored) — the engine compares the two and surfaces a
115
128
  * divergence observation. Absent on non-resume ops and from pre-1.360 engines (adapters treat absence
116
129
  * as the legacy current-config behavior).
130
+ *
131
+ * A frozen copy holding exactly the declared members ({@link WORKSPACE_HANDLE_MEMBERS}) the checkpoint row carries
132
+ * as strings — the SAME object the env factory's {@link ExecutionEnvFactoryContext.resumeWorkspace} was, for the
133
+ * same row — not the stored row's own object: writing to it throws, and a decoration on the stored handle is absent.
117
134
  */
118
- priorHandle?: WorkspaceHandle;
135
+ priorHandle?: Readonly<WorkspaceHandle>;
119
136
  }
120
137
  /**
121
138
  * One chunk streamed from {@link RemoteExecutionEnv.execStream} (design/48 §5 gap 3, council BUG#3). A run is a
@@ -494,6 +511,29 @@ export interface ExecutionEnvFactoryContext {
494
511
  * already shares the parent env (and its cwd).
495
512
  */
496
513
  parentCwd?: string;
514
+ /**
515
+ * design/380 O1 delivery fence — the workspace a DURABLE RESUME leg is rebuilding: a frozen copy of the
516
+ * {@link WorkspaceHandle} the checkpoint row recorded at suspend, holding exactly the members a handle declares
517
+ * ({@link WORKSPACE_HANDLE_MEMBERS}) that the row carries as strings. Present ONLY on that leg — the key is
518
+ * absent (not an `undefined` value) on every fresh run, and on a resume whose row recorded no workspace
519
+ * handle. Mutating the copy reaches nothing the engine reads.
520
+ *
521
+ * A factory bound to a target (the device lane) **MUST** mint the env on the device
522
+ * `resumeWorkspace.deviceId` names and, when it cannot (offline, busy, the placement moved to another
523
+ * device), **THROW** a typed refusal. It **MUST NOT** connect some other device and leave the engine to
524
+ * notice, and it **MUST NOT** look the current binding up afresh from {@link placementRootSessionId} for
525
+ * this leg. A row recorded without `deviceId` (a non-device lane, or a row older than the field) states no
526
+ * device to honor; the factory then follows its ordinary routing.
527
+ *
528
+ * The minted env's `workspaceHandle()` **MUST** report the device its connection actually reached; it is
529
+ * never copied from `resumeWorkspace`. The engine's comparison of that report against the row (`provider`
530
+ * equal; `deviceId` equal whenever the row carries one) stays as the receipt check and TRUSTS the report: a
531
+ * factory that connected elsewhere and reports so is refused (`resume.env_failed`), only after the wrong
532
+ * connection was made, but one that echoes the fence while connecting elsewhere defeats its own protection
533
+ * and passes. The fence moves no refusal earlier than it was: it runs after the claim, so what a deployment
534
+ * can still catch BEFORE the claim is `RunnerDeps.resumePreflight` alone.
535
+ */
536
+ resumeWorkspace?: Readonly<WorkspaceHandle>;
497
537
  }
498
538
  /**
499
539
  * A **trusted control-plane** factory that mints a per-task {@link ExecutionEnv} (design/48 §5 answers core
@@ -1,3 +1,21 @@
1
+ const WORKSPACE_HANDLE_MEMBER_TABLE = { sandboxId: 1, provider: 1, mountPath: 1, snapshotId: 1, sessionToken: 1, restoreMode: 1, deviceId: 1 };
2
+ export const WORKSPACE_HANDLE_MEMBERS = Object.keys(WORKSPACE_HANDLE_MEMBER_TABLE);
3
+ const FROZEN_COPY_OF = new WeakMap();
4
+ export function frozenHandleCopy(handle) {
5
+ const known = FROZEN_COPY_OF.get(handle);
6
+ if (known !== undefined)
7
+ return known;
8
+ const copy = {};
9
+ for (const member of WORKSPACE_HANDLE_MEMBERS) {
10
+ const value = handle[member];
11
+ if (typeof value === "string")
12
+ copy[member] = value;
13
+ }
14
+ const frozen = Object.freeze(copy);
15
+ if (typeof handle === "object" && handle !== null)
16
+ FROZEN_COPY_OF.set(handle, frozen);
17
+ return frozen;
18
+ }
1
19
  export const RETRYABLE_REMOTE_ERROR_CODES = [
2
20
  "auth_transient",
3
21
  "connect_failed",
@@ -23,6 +23,7 @@
23
23
  * unsafe payload, an input the constraints refuse): correct the decision and resubmit.
24
24
  */
25
25
  import type { AssertAllKeysHandled } from "./ask-origin.js";
26
+ import type { TerminalFailureDetail } from "./terminal-cause.js";
26
27
  /**
27
28
  * design/80 D-1: why a consumed checkpoint was {@link import("./checkpoint-store.js").CheckpointStore.reopen | reopened}
28
29
  * (resolved → pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
@@ -51,6 +52,23 @@ export type ResumeStartupCause = (typeof RESUME_STARTUP_CAUSES)[number];
51
52
  /** The cause a resume failure's code names; `undefined` when the code is absent or the table does not
52
53
  * name it. Every `config.*` code is a configuration door's refusal. */
53
54
  export declare function resumeStartupCauseOf(code: string | undefined): ResumeStartupCause | undefined;
55
+ /**
56
+ * The members of a resume refusal's `detail` the parked-resume receipt carries beside its `cause` — the three a
57
+ * {@link import("./checkpoint-store.js").CheckpointError} states where its `code` alone does not say which input or which arm refused:
58
+ * `code` (on `checkpoint.config_refused`: prepare's own code, the word a fresh run of the same bad config reports), `field` (which
59
+ * input carries the refusal) and `reason` (which arm fired). Their words are those families' own; the receipt adds no word of its own.
60
+ */
61
+ export declare const RESUME_REFUSAL_MEMBERS: readonly ["code", "field", "reason"];
62
+ /** {@link RESUME_REFUSAL_MEMBERS} as the receipt carries them: each a string, each present only when the failure stated it. */
63
+ export type ResumeRefusalDetail = {
64
+ readonly [K in (typeof RESUME_REFUSAL_MEMBERS)[number]]?: string;
65
+ };
66
+ /**
67
+ * The refusal members a failed resume stated, read from the failure's coded account ({@link TerminalFailureDetail}, `kind: "coded"`:
68
+ * the `detail` its throw site stated) — never from its sentence. `undefined` when the failure carries no coded account or none of
69
+ * the three members is a string in it.
70
+ */
71
+ export declare function resumeRefusalDetailOf(account: TerminalFailureDetail | undefined): ResumeRefusalDetail | undefined;
54
72
  declare const REOPEN_REASON_OF_CAUSE: {
55
73
  readonly environment: "env_failed";
56
74
  readonly session_gone: "env_failed";
@@ -21,6 +21,18 @@ export function resumeStartupCauseOf(code) {
21
21
  return undefined;
22
22
  return CAUSE_OF_CODE.get(code) ?? (code.startsWith("config.") ? "configuration" : undefined);
23
23
  }
24
+ export const RESUME_REFUSAL_MEMBERS = Object.freeze(["code", "field", "reason"]);
25
+ export function resumeRefusalDetailOf(account) {
26
+ if (account === undefined || account.kind !== "coded")
27
+ return undefined;
28
+ const out = {};
29
+ for (const member of RESUME_REFUSAL_MEMBERS) {
30
+ const value = Object.hasOwn(account.detail, member) ? account.detail[member] : undefined;
31
+ if (typeof value === "string")
32
+ out[member] = value;
33
+ }
34
+ return Object.keys(out).length > 0 ? out : undefined;
35
+ }
24
36
  const REOPEN_REASON_OF_CAUSE = {
25
37
  environment: "env_failed",
26
38
  session_gone: "env_failed",
@@ -0,0 +1,46 @@
1
+ /**
2
+ * What the approving person wrote on the card ({@link ResumeOutcome} `policy_ask` `allow` `feedback`) — the one place that
3
+ * says what counts as written, which decide may carry it, and how it joins the resume continuation.
4
+ *
5
+ * ONE READING of "did the person write anything" ({@link writtenFeedback}): the text with the whitespace the card's input does
6
+ * not keep removed, or nothing when that leaves nothing. The pre-CAS checks and the continuation both ask it, so they cannot
7
+ * disagree about a blank value. (A deny's `reason` keeps its own rule, `decidersNote`: only `""` is blank there.)
8
+ *
9
+ * The words ride the resume continuation user message — the first message after the batch's tool results, once the batch's
10
+ * unanswered siblings have settled — as a user-lane range of their own: outside every engine segment, so a derived view reads
11
+ * them as user speech; verbatim, no fence, no wrapper sentence, no length bound. They are owed with the rest of the
12
+ * continuation: when the batch parks again before the prompt lands they ride the new row as its last parked steer.
13
+ */
14
+ import { type PendingSteerEntry, type ResumeOutcome } from "../checkpoint-store.js";
15
+ type PolicyAsk = Extract<ResumeOutcome, {
16
+ gate: "policy_ask";
17
+ }>;
18
+ /** The words as the model is to read them, or `undefined` when nothing was written (absent, empty, whitespace-only). */
19
+ export declare function writtenFeedback(feedback: string | undefined): string | undefined;
20
+ /**
21
+ * Pre-CAS: a WRITTEN feedback is the allow arm's, a person's, and free of the `<system-reminder>` tag (opening or closing — the one
22
+ * judge `sanitizeUntrustedText`, the same rule a deny's `reason` is held to, applied here by the same call). Each is refused
23
+ * (`checkpoint.invalid_outcome`, the row stays pending for a well-formed decide) rather than dropped or delivered as something it
24
+ * is not: a deny's words are `reason`, and a machine's text must never reach the model as the user's. The twin is the captured
25
+ * plain data (its text already held to the string domain by the capture), never the caller's object.
26
+ */
27
+ export declare function refuseUnfitFeedback(twin: PolicyAsk): void;
28
+ /** The words this resume is to deliver: a person's `allow` carrying a written feedback (the pre-CAS checks have run), else `undefined`. */
29
+ export declare function approvalWordsOf(outcome: ResumeOutcome): string | undefined;
30
+ /**
31
+ * The continuation with the words as its last range. The separator glue before them is engine bytes (its own segment), so the
32
+ * stored message's user-lane range is exactly the words. Appends to `engineSegments` in place; the caller owns both.
33
+ */
34
+ export declare function withApprovalWords(continuation: string, engineSegments: Array<{
35
+ start: number;
36
+ end: number;
37
+ }>, words: string): string;
38
+ /**
39
+ * The words as the continuation's owed content: the last parked steer, an untrusted one (the carrier that exists for an owed instruction).
40
+ * The frame is remembered by identity ({@link isApprovalWordsFrame}) — never by a member, which the steer store would refuse as an unknown
41
+ * key — so the owed account can count it as what it is (`detail.kind: "approval_feedback"`) and tell a recoverable retry from a loss.
42
+ */
43
+ export declare function owedApprovalWords(words: string | undefined, inputId: string): Array<Omit<PendingSteerEntry, "seq">>;
44
+ /** Whether `frame` is one {@link owedApprovalWords} minted. */
45
+ export declare function isApprovalWordsFrame(frame: object): boolean;
46
+ export {};
@@ -0,0 +1,41 @@
1
+ import { CheckpointError } from "../checkpoint-store.js";
2
+ import { sanitizeUntrustedText, sentinelTagRefusal } from "../untrusted-text.js";
3
+ export function writtenFeedback(feedback) {
4
+ const trimmed = feedback?.trim();
5
+ return trimmed === undefined || trimmed === "" ? undefined : trimmed;
6
+ }
7
+ export function refuseUnfitFeedback(twin) {
8
+ const words = writtenFeedback(twin.feedback);
9
+ if (words === undefined)
10
+ return;
11
+ if (twin.decision !== "allow") {
12
+ throw new CheckpointError("checkpoint.invalid_outcome", "resume carried `feedback` on a `deny` — feedback is the approving person's words on an `allow` (a denial's words are `reason`); refusing rather than dropping it silently, the checkpoint stays pending");
13
+ }
14
+ const byPerson = twin.hostDecision.decidedBy === "person";
15
+ if (!byPerson) {
16
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carried \`feedback\` on a decide made by "${twin.hostDecision.decidedBy}" — feedback is delivered to the model as the user's own words, which only a person's decide can be; refusing rather than attributing a machine's text to the user, the checkpoint stays pending`);
17
+ }
18
+ if (sanitizeUntrustedText(words) !== words) {
19
+ throw new CheckpointError("checkpoint.invalid_outcome", sentinelTagRefusal("resume allow feedback"));
20
+ }
21
+ }
22
+ export function approvalWordsOf(outcome) {
23
+ return outcome.gate === "policy_ask" && outcome.decision === "allow" ? writtenFeedback(outcome.feedback) : undefined;
24
+ }
25
+ export function withApprovalWords(continuation, engineSegments, words) {
26
+ const glueStart = continuation.length;
27
+ const withGlue = `${continuation}\n\n`;
28
+ engineSegments.push({ start: glueStart, end: withGlue.length });
29
+ return withGlue + words;
30
+ }
31
+ const APPROVAL_WORDS_FRAMES = new WeakSet();
32
+ export function owedApprovalWords(words, inputId) {
33
+ if (words === undefined)
34
+ return [];
35
+ const frame = { text: words, trusted: false, inputId };
36
+ APPROVAL_WORDS_FRAMES.add(frame);
37
+ return [frame];
38
+ }
39
+ export function isApprovalWordsFrame(frame) {
40
+ return APPROVAL_WORDS_FRAMES.has(frame);
41
+ }
@@ -23,13 +23,13 @@ import { type CompactionSettings } from "../../internal/harness.js";
23
23
  * because reading it at the moment of the pass — not at prepare — is the whole of what it states.
24
24
  */
25
25
  /**
26
- * Opt-in working-file attachments (LONGRUN-2 / CC 198 post-compact restore parity).
26
+ * The post-compaction restore (LONGRUN-2; the reference's post-compact file restore, cli296.js:249523 `attachments`).
27
27
  *
28
28
  * CC parity: post-compact file re-read is DEFAULT ON (CC hard-codes it;
29
29
  * LONGRUN-2 measured ≈2.3 extra read round-trips per compaction without it). `false` opts out; an
30
30
  * object customizes caps. Selection set = most recently READ (CC readFileState semantics) with a
31
- * modified-files fallback inside maybeCompact. Returns `undefined` when attachments are off or the
32
- * task has no hands (no `readTaskFile` ⇒ nothing can be re-read).
31
+ * modified-files fallback inside maybeCompact. Returns `undefined` when the restore is off or the
32
+ * task has no hands (no `compactionRestore` ⇒ nothing can be re-read).
33
33
  */
34
34
  export declare function buildWorkingFileAttachments(spec: TaskSpec, prepared: Prepared): MaybeCompactOptions["workingFileAttachments"];
35
35
  /**
@@ -3,10 +3,10 @@ import { buildToolResultRef, OFFLOAD_TOOL_NAME, PERSISTED_OUTPUT_PREFIX, toolRes
3
3
  import { compactionEnabled, contextOverlayFor } from "../context-settings.js";
4
4
  import { DEFAULT_COMPACTION_SETTINGS } from "../../internal/harness.js";
5
5
  export function buildWorkingFileAttachments(spec, prepared) {
6
- if (spec.compaction?.attachWorkingFiles === false || !prepared.readTaskFile)
6
+ if (spec.compaction?.attachWorkingFiles === false || !prepared.compactionRestore)
7
7
  return undefined;
8
8
  return {
9
- readFile: prepared.readTaskFile,
9
+ ...prepared.compactionRestore,
10
10
  ...(prepared.recentlyReadFiles ? { recentlyReadFiles: prepared.recentlyReadFiles } : {}),
11
11
  ...(prepared.normalizeAttachmentPath ? { normalizePath: prepared.normalizeAttachmentPath } : {}),
12
12
  ...(prepared.isDedupStubResult ? { isDedupStubResult: prepared.isDedupStubResult } : {}),
@@ -234,7 +234,7 @@ export interface FrameReceipt {
234
234
  text: string;
235
235
  /** Which frame this receipt settles. A boundary collector must not render a listing whose receipt is
236
236
  * still pending (the frame is queued, not yet in the transcript). */
237
- source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style";
237
+ source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style" | "approval_feedback";
238
238
  /** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
239
239
  * and parks any session write for the next serialization point. */
240
240
  commit: (entryId: string) => void;
@@ -396,17 +396,15 @@ export interface Prepared {
396
396
  effectiveReadFace?: ContainmentFace;
397
397
  /** owned — the same observation on the WRITE seat (`TaskResult.effectiveWriteFace`). */
398
398
  effectiveWriteFace?: ContainmentFace;
399
- /** The normalized deny ADDITIONS in force (deployment ∪ task ∪ checkpoint seed; built-ins
400
- * excluded), echoed on `TaskResult.effectiveReadDenyPatterns`. Present iff non-empty; a defensive
401
- * copy (the wide-scope working array stays the engine's own). */
399
+ /** The normalized deny ADDITIONS in force (deployment ∪ task ∪ checkpoint seed; built-ins excluded), echoed on
400
+ * `TaskResult.effectiveReadDenyPatterns`. Present iff non-empty; a defensive copy (the working array stays the engine's). */
402
401
  effectiveReadDenyPatterns?: readonly import("../../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
403
402
  /** The memory-visibility observation prepareMemory minted (echoed on
404
403
  * `TaskResult.effectiveMemoryScopes`). Always present on a completed prepare (the memory-less
405
404
  * states are their own values); the seat is optional only so a Prepared shape without the phase
406
405
  * cannot fabricate one. */
407
406
  effectiveMemoryScopes?: import("../types.js").EffectiveMemoryScopes;
408
- /** The per-task logical cwd ref when a real shell is mounted (else undefined). The Runner
409
- * reads `cwdRef.current` after each tool to detect a `cd` move and emit `workspace_changed`. */
407
+ /** The per-task logical cwd ref when a real shell is mounted (else undefined); read after each tool for `workspace_changed`. */
410
408
  cwdRef?: CwdRef;
411
409
  /** The root the hand tools resolve root-relative spellings against (present with hands; read-only hands included). */
412
410
  handsRoot?: string;
@@ -478,6 +476,11 @@ export interface Prepared {
478
476
  * admission died with that leg, so the resumed bytes are judged (and admitted for their pre-spawn re-establishment)
479
477
  * on THIS leg — a deny row found now vetoes, an operator's edit is judged on the `rejudge` seat. */
480
478
  shellReadCheck?: import("../../tools/fs/shell-read-check.js").ShellReadCheck;
479
+ crossBoundary?: {
480
+ readonly check: import("../cross-boundary-taint.js").CrossBoundaryCheck | undefined;
481
+ settle(): Promise<void>;
482
+ storeWaits(): number;
483
+ };
481
484
  /** The ORG adjudication face, re-resolved on a durable RESUME before an approved
482
485
  * pending call executes. The resume path bypasses the harness gate by design (a human already
483
486
  * adjudicated the checkpointed call), which is exactly where org policy skew is most likely: the
@@ -1004,56 +1007,42 @@ export interface Prepared {
1004
1007
  * calls themselves never pass through the recorded seam (they ride the separate compaction-brain
1005
1008
  * wrapper), so the snapshot is never polluted by a summary request. */
1006
1009
  lastBrainContext: () => CompactionForkContext | undefined;
1007
- /** Narrow workspace reader for compaction working-file attachments (LONGRUN-2): reads a task file
1008
- * via the SAME env the hands ran against (so remote/k8s/E2B tasks read the container's tree, not
1009
- * the control plane's). Present only when the hands are enabled — without an env there is no
1010
- * workspace to re-read. null = unreadable (deleted/binary/transport error); callers skip it.
1011
- * `{ withheld }` = the target matches the sensitive-path read deny list — the
1012
- * attachment is deliberately withheld and the consumer must SAY so (an annotation, never a silent
1013
- * skip: a silently missing attachment reads as "file gone", which is a different fact). */
1014
- readTaskFile?: (path: string) => Promise<string | null | {
1015
- withheld: {
1016
- pattern: string;
1017
- };
1018
- }>;
1010
+ /** The post-compaction restore (`MaybeCompactOptions.workingFileAttachments.restore`): re-reads the candidates through
1011
+ * the leg's one file reader under the run's synthetic-Read judge — the user attachment's own path read — deduplicated
1012
+ * against the read state the summary leaves, and mints their attachment frames (`readCompactionRestore`); a file it
1013
+ * does not deliver — or whose frame the transcript does not take — is reported (`onError` phase `degraded`,
1014
+ * classification `attachment-read`). Present only with hands. */
1015
+ compactionRestore?: Required<Pick<NonNullable<import("../auto-compaction.js").MaybeCompactOptions["workingFileAttachments"]>, "restore" | "undelivered">>;
1019
1016
  /** CC post-compact restore parity (2026-07-03): the task's READ files, most recent
1020
- * first (from the hands' readFileState `lastReadAt` stamps). The compaction working-file
1021
- * attachment prefers this over the modified set — CC restores what the model RECENTLY READ,
1022
- * including untouched reference files. Present only with hands (same gate as readTaskFile).
1023
- * Seeded entries are excluded — their content lives in the system-prompt lane, which
1024
- * compaction never touches, so re-attaching them into the summary would be a duplicate. */
1017
+ * first (from the hands' readFileState `lastReadAt` stamps). The restore prefers this over the modified set — CC
1018
+ * restores what the model RECENTLY READ, including untouched reference files. Present only with hands.
1019
+ * Seeded entries are excluded — their content lives in the system-prompt lane, which compaction never touches. */
1025
1020
  recentlyReadFiles?: () => string[];
1026
1021
  /** Canonicalizes a raw (often relative, model-typed) path the
1027
- * SAME way the hands toolkit does (`resolveKey` against the same containment root `readTaskFile`
1028
- * uses), so `maybeCompact`'s kept-tail/instruction-source exclusions compare paths in the SAME
1022
+ * SAME way the hands toolkit does (`resolveKey` against the hands' containment root), so `maybeCompact`'s
1023
+ * kept-tail/instruction-source exclusions compare paths in the SAME
1029
1024
  * coordinate `recentlyReadFiles()` already uses — without this a plain string match silently never
1030
- * fires for the common relative-vs-canonical case. Present only with hands (same gate as
1031
- * readTaskFile); absent there is no containment root to resolve against, and every path source is
1032
- * already in the same raw coordinate anyway. */
1025
+ * fires for the common relative-vs-canonical case. Present only with hands; absent there is no
1026
+ * containment root to resolve against, and every path source is already in the same raw coordinate anyway. */
1033
1027
  normalizeAttachmentPath?: (raw: string) => Promise<string>;
1034
1028
  /** Recognizes the hands Read tool's own dedup-stub markers
1035
1029
  * ({@link isReadDedupStubResult}), so `maybeCompact`'s kept-tail scan can tell a SUBSTANTIVE Read
1036
1030
  * result from a stub hit whose original full transmission may already be summarized away. Present
1037
- * only with hands (same gate as readTaskFile). */
1031
+ * only with hands. */
1038
1032
  isDedupStubResult?: (resultText: string) => boolean;
1039
1033
  /** Wired to `MaybeCompactOptions.onApplied` by every compaction lane (CC 220 clears readFileState at its
1040
- * compaction landing site, @388663). Drops the non-seeded read-state entries
1041
- * (the summary just replaced the Read results the dedup stubs point at) and re-registers the files
1042
- * that were re-attached WHOLE. Present only with hands (same gate as readTaskFile).
1043
- * `preserveReadState` carries the kept-tail-visible files that were
1044
- * deliberately skipped for re-attachment — their existing entry must survive the clear too (same
1045
- * reasoning as a seeded entry: the model's view of the file did not change).
1034
+ * compaction landing site, @388663). Drops every read-state entry the summary took out of view
1035
+ * (`readFileStateAfterSummary`); the restored files then re-register through their frames' own commits. Present
1036
+ * only with hands. `preserveReadState` carries the files the context the summary leaves still shows — their
1037
+ * existing entry survives the clear (the model's view of the file did not change).
1046
1038
  *
1047
1039
  * SCOPE — read "every compaction lane" as exactly that and no wider. The request-layer
1048
1040
  * stale-result pass takes a Read body off the wire with no compaction at all, and retires its own
1049
1041
  * entries at its own landing site (`prepare-context-lane.ts`'s `landStaleResultClears`) through the
1050
- * SAME function this seat calls, on its `tool-results-cleared` arm: ONE retirement mechanism, two
1051
- * ways for content to leave. Taken as a claim about every SHRINK mechanism this sentence was true
1042
+ * SAME function this seat calls, on its `transmissions-gone` arm (the guard trim's dropped tool results and attachment frames too):
1043
+ * ONE retirement mechanism, several ways for content to leave. Taken as a claim about every SHRINK mechanism this sentence was true
1052
1044
  * of the lanes it named and silently false of the one it did not. */
1053
- onCompactionApplied?: (attachedComplete: ReadonlyArray<{
1054
- path: string;
1055
- content: string;
1056
- }>, preserveReadState?: ReadonlyArray<string>) => void;
1045
+ onCompactionApplied?: (preserveReadState: ReadonlyArray<string>) => void;
1057
1046
  /** The live diagnostics lane (present only when the gate passed — manager w/ registry +
1058
1047
  * write hands + not opted out). `registry` is drained by runtask at turn boundaries; `nudge` is
1059
1048
  * called (fire-and-forget) after each successful edit/write so the language server re-analyzes. */
@@ -1725,6 +1714,9 @@ export interface RunInternals {
1725
1714
  };
1726
1715
  contentSafety: import("../memory-engine/delegation-provenance.js").DelegationContentSafety;
1727
1716
  };
1717
+ /** The run TREE's cross-boundary token — the tree's ONE state is reached from it by the engine alone: the spawning leg hands its
1718
+ * own down (the delegation and workflow lanes), so a bit any leg sets is read by every other at its next judgment; a root leg mints one. */
1719
+ crossBoundaryTree?: import("../cross-boundary-taint.js").CrossBoundaryTreeToken;
1728
1720
  /**
1729
1721
  * The SESSION-scoped org-admission freeze for a SAME-SESSION continuation
1730
1722
  * that is NOT a checkpoint resume: a retained background child revived through the in-process resume