@sema-agent/core 7.52.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 (357) hide show
  1. package/CHANGELOG.md +470 -0
  2. package/LICENSE +3 -1
  3. package/README.md +3 -1
  4. package/dist/agents/cross-session-envelope.d.ts +8 -0
  5. package/dist/agents/cross-session-envelope.js +6 -0
  6. package/dist/agents/cross-session-judge.d.ts +21 -1
  7. package/dist/agents/cross-session-judge.js +8 -0
  8. package/dist/agents/cross-session-ref.d.ts +5 -0
  9. package/dist/agents/cross-session-ref.js +1 -0
  10. package/dist/agents/org-delivery-gate.d.ts +122 -0
  11. package/dist/agents/org-delivery-gate.js +69 -0
  12. package/dist/agents/peer-held-queue.d.ts +9 -4
  13. package/dist/agents/peer-held-queue.js +6 -5
  14. package/dist/agents/peer-session-drain.d.ts +35 -6
  15. package/dist/agents/peer-session-drain.js +271 -182
  16. package/dist/agents/read-inbox-tool.d.ts +7 -0
  17. package/dist/agents/read-inbox-tool.js +5 -1
  18. package/dist/agents/send-message-tool.js +3 -4
  19. package/dist/agents/subagent.d.ts +6 -5
  20. package/dist/agents/subagent.js +117 -83
  21. package/dist/agents/tool-filter.d.ts +25 -4
  22. package/dist/agents/tool-filter.js +13 -3
  23. package/dist/brain/anthropic.d.ts +6 -1
  24. package/dist/brain/anthropic.js +4 -4
  25. package/dist/brain/model-presets.js +2 -1
  26. package/dist/brain/route-adjudicator.d.ts +6 -0
  27. package/dist/brain/routing.d.ts +24 -1
  28. package/dist/brain/routing.js +54 -19
  29. package/dist/brain/timeout.js +5 -4
  30. package/dist/core/a2a.d.ts +5 -2
  31. package/dist/core/a2a.js +33 -14
  32. package/dist/core/allow-entry.d.ts +3 -1
  33. package/dist/core/allow-entry.js +3 -2
  34. package/dist/core/approval-object.d.ts +87 -0
  35. package/dist/core/approval-object.js +85 -0
  36. package/dist/core/arg-summary.d.ts +3 -2
  37. package/dist/core/arg-summary.js +17 -11
  38. package/dist/core/ask-origin.d.ts +43 -27
  39. package/dist/core/ask-origin.js +20 -11
  40. package/dist/core/ask-question.d.ts +0 -1
  41. package/dist/core/ask-question.js +4 -67
  42. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  43. package/dist/core/auto-compaction.d.ts +106 -50
  44. package/dist/core/auto-compaction.js +69 -53
  45. package/dist/core/auto-mode-prompt.d.ts +4 -2
  46. package/dist/core/auto-mode-prompt.js +4 -3
  47. package/dist/core/canonical-json.d.ts +69 -0
  48. package/dist/core/canonical-json.js +130 -8
  49. package/dist/core/checkpoint-requirements.d.ts +13 -7
  50. package/dist/core/checkpoint-requirements.js +4 -1
  51. package/dist/core/checkpoint-store.d.ts +60 -36
  52. package/dist/core/checkpoint-store.js +7 -6
  53. package/dist/core/child-clamp-seats.d.ts +45 -6
  54. package/dist/core/child-clamp-seats.js +56 -4
  55. package/dist/core/compliance.js +3 -1
  56. package/dist/core/consolidate-scope.d.ts +1 -1
  57. package/dist/core/cross-boundary-taint.d.ts +213 -0
  58. package/dist/core/cross-boundary-taint.js +196 -0
  59. package/dist/core/decision-receipt.d.ts +44 -1
  60. package/dist/core/decision-receipt.js +16 -0
  61. package/dist/core/effective-path-target.d.ts +28 -6
  62. package/dist/core/effective-path-target.js +28 -2
  63. package/dist/core/engine-notice.d.ts +47 -4
  64. package/dist/core/engine-notice.js +17 -1
  65. package/dist/core/engine-storage-backend.d.ts +73 -0
  66. package/dist/core/engine-storage-backend.js +1 -0
  67. package/dist/core/file-history-store.d.ts +6 -2
  68. package/dist/core/file-history-store.js +1 -0
  69. package/dist/core/gate-fold.js +13 -4
  70. package/dist/core/gate-lanes.js +41 -34
  71. package/dist/core/gate-outcome.d.ts +18 -9
  72. package/dist/core/governance-codes.d.ts +9 -2
  73. package/dist/core/governance-codes.js +9 -0
  74. package/dist/core/hands-band-screen.d.ts +7 -0
  75. package/dist/core/hands-band-screen.js +32 -0
  76. package/dist/core/hooks.d.ts +21 -19
  77. package/dist/core/hooks.js +6 -5
  78. package/dist/core/locked-config.d.ts +110 -31
  79. package/dist/core/locked-config.js +155 -42
  80. package/dist/core/lsp.d.ts +1 -1
  81. package/dist/core/mcp-probe.js +1 -1
  82. package/dist/core/mcp-server-spec.d.ts +152 -17
  83. package/dist/core/mcp-server-spec.js +223 -40
  84. package/dist/core/mcp.d.ts +21 -14
  85. package/dist/core/mcp.js +59 -54
  86. package/dist/core/memory-engine/engine.d.ts +14 -19
  87. package/dist/core/memory-engine/engine.js +9 -6
  88. package/dist/core/memory-engine/export-bundle.d.ts +1 -1
  89. package/dist/core/memory-engine/export-bundle.js +1 -1
  90. package/dist/core/memory-engine/file-backend.d.ts +2 -6
  91. package/dist/core/memory-engine/file-backend.js +1 -12
  92. package/dist/core/memory.d.ts +3 -3
  93. package/dist/core/mode-policy.d.ts +16 -26
  94. package/dist/core/mode-policy.js +5 -18
  95. package/dist/core/mods/kernel/dispatch.js +13 -15
  96. package/dist/core/mods/kernel/events.d.ts +9 -6
  97. package/dist/core/mods/kernel/events.js +2 -2
  98. package/dist/core/mods/kernel/receive.d.ts +41 -11
  99. package/dist/core/mods/kernel/receive.js +124 -54
  100. package/dist/core/mods/kernel/site-rules.d.ts +80 -15
  101. package/dist/core/mods/kernel/site-rules.js +232 -40
  102. package/dist/core/mods/stations.d.ts +0 -6
  103. package/dist/core/mods/stations.js +0 -7
  104. package/dist/core/mounted-mcp.d.ts +57 -0
  105. package/dist/core/mounted-mcp.js +71 -2
  106. package/dist/core/own-property-receipt.d.ts +13 -0
  107. package/dist/core/own-property-receipt.js +3 -1
  108. package/dist/core/parent-spec-seat.d.ts +12 -15
  109. package/dist/core/parent-spec-seat.js +14 -6
  110. package/dist/core/pause-registry.d.ts +35 -1
  111. package/dist/core/pause-registry.js +10 -0
  112. package/dist/core/permission-rule-consent.d.ts +18 -5
  113. package/dist/core/permission-rule-consent.js +13 -8
  114. package/dist/core/permission-rule-model.d.ts +87 -15
  115. package/dist/core/permission-rule-model.js +87 -8
  116. package/dist/core/permission-rule-org.d.ts +200 -33
  117. package/dist/core/permission-rule-org.js +217 -91
  118. package/dist/core/permission-rule-session.js +2 -2
  119. package/dist/core/permission-rule-store.d.ts +8 -0
  120. package/dist/core/permission-rule-store.js +1 -0
  121. package/dist/core/permission-rule-sync.js +3 -2
  122. package/dist/core/permission-rules.d.ts +7 -1
  123. package/dist/core/permission-rules.js +37 -8
  124. package/dist/core/read-only-shell.d.ts +1 -1
  125. package/dist/core/read-only-shell.js +30 -9
  126. package/dist/core/remote-env.d.ts +41 -1
  127. package/dist/core/remote-env.js +18 -0
  128. package/dist/core/resume-startup-cause.d.ts +18 -0
  129. package/dist/core/resume-startup-cause.js +12 -0
  130. package/dist/core/retention.js +7 -2
  131. package/dist/core/retired-keys.d.ts +8 -2
  132. package/dist/core/retired-keys.js +12 -1
  133. package/dist/core/roles.js +60 -10
  134. package/dist/core/rule-lane-reads.js +1 -1
  135. package/dist/core/runner/approval-feedback.d.ts +46 -0
  136. package/dist/core/runner/approval-feedback.js +41 -0
  137. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  138. package/dist/core/runner/compaction-call-options.js +2 -2
  139. package/dist/core/runner/contracts.d.ts +34 -41
  140. package/dist/core/runner/cross-boundary-lane.d.ts +87 -0
  141. package/dist/core/runner/cross-boundary-lane.js +190 -0
  142. package/dist/core/runner/decide-continuation.d.ts +4 -4
  143. package/dist/core/runner/decide-continuation.js +3 -1
  144. package/dist/core/runner/gate-exit.d.ts +32 -12
  145. package/dist/core/runner/gate-exit.js +77 -34
  146. package/dist/core/runner/inherited-ask-grants.js +2 -2
  147. package/dist/core/runner/mcp-redial.d.ts +52 -10
  148. package/dist/core/runner/mcp-redial.js +38 -9
  149. package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
  150. package/dist/core/runner/permission-rule-lanes.js +22 -10
  151. package/dist/core/runner/person-path-rows.d.ts +5 -3
  152. package/dist/core/runner/person-path-rows.js +2 -2
  153. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -4
  154. package/dist/core/runner/prepare-acquire-reconcile.js +3 -3
  155. package/dist/core/runner/prepare-ask-lane.js +3 -3
  156. package/dist/core/runner/prepare-caps-and-workflow.d.ts +6 -5
  157. package/dist/core/runner/prepare-caps-and-workflow.js +3 -3
  158. package/dist/core/runner/prepare-config-doors.d.ts +58 -15
  159. package/dist/core/runner/prepare-config-doors.js +167 -146
  160. package/dist/core/runner/prepare-context-lane.js +6 -3
  161. package/dist/core/runner/prepare-defer-classify.js +4 -7
  162. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  163. package/dist/core/runner/prepare-delegation-surface.js +8 -7
  164. package/dist/core/runner/prepare-execution-env.d.ts +1 -2
  165. package/dist/core/runner/prepare-execution-env.js +2 -1
  166. package/dist/core/runner/prepare-gate-stations.d.ts +2 -0
  167. package/dist/core/runner/prepare-gate-stations.js +4 -3
  168. package/dist/core/runner/prepare-hands-readface.d.ts +3 -3
  169. package/dist/core/runner/prepare-hands-readface.js +3 -2
  170. package/dist/core/runner/prepare-inherited-gate.d.ts +2 -0
  171. package/dist/core/runner/prepare-inherited-gate.js +3 -2
  172. package/dist/core/runner/prepare-listings.d.ts +1 -1
  173. package/dist/core/runner/prepare-listings.js +4 -2
  174. package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
  175. package/dist/core/runner/prepare-memory-engine-session.js +19 -6
  176. package/dist/core/runner/prepare-memory.d.ts +3 -2
  177. package/dist/core/runner/prepare-memory.js +6 -2
  178. package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
  179. package/dist/core/runner/prepare-offload-wrappers.js +2 -1
  180. package/dist/core/runner/prepare-park-ask.d.ts +1 -1
  181. package/dist/core/runner/prepare-park-ask.js +9 -3
  182. package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
  183. package/dist/core/runner/prepare-policy-chain.js +14 -19
  184. package/dist/core/runner/prepare-project-context.d.ts +1 -1
  185. package/dist/core/runner/prepare-project-context.js +6 -4
  186. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  187. package/dist/core/runner/prepare-prompt-assembly.js +20 -16
  188. package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
  189. package/dist/core/runner/prepare-prompt-inputs.js +6 -3
  190. package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
  191. package/dist/core/runner/prepare-protocol-tools.js +19 -12
  192. package/dist/core/runner/prepare-question-face.d.ts +4 -2
  193. package/dist/core/runner/prepare-question-face.js +4 -3
  194. package/dist/core/runner/prepare-safety-scan.d.ts +7 -2
  195. package/dist/core/runner/prepare-safety-scan.js +34 -27
  196. package/dist/core/runner/prepare-suspend-saga.d.ts +2 -2
  197. package/dist/core/runner/prepare-suspend-saga.js +6 -2
  198. package/dist/core/runner/prepare-task.js +22 -19
  199. package/dist/core/runner/prepare-turn-wiring.d.ts +10 -5
  200. package/dist/core/runner/prepare-turn-wiring.js +14 -49
  201. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -5
  202. package/dist/core/runner/prepare-wiring-manifest.js +9 -6
  203. package/dist/core/runner/prepare-workspace-restore.js +2 -2
  204. package/dist/core/runner/resume-admission.d.ts +4 -1
  205. package/dist/core/runner/resume-admission.js +16 -14
  206. package/dist/core/runner/resume-apply.js +4 -4
  207. package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
  208. package/dist/core/runner/resume-internals-and-config.js +3 -8
  209. package/dist/core/runner/resume-policy-outcome.d.ts +2 -2
  210. package/dist/core/runner/resume-policy-outcome.js +18 -15
  211. package/dist/core/runner/resume-preflight.js +2 -2
  212. package/dist/core/runner/resume-review-outcome.js +3 -3
  213. package/dist/core/runner/retired-runner-deps.d.ts +5 -6
  214. package/dist/core/runner/retired-runner-deps.js +1 -4
  215. package/dist/core/runner/run-leg.js +97 -43
  216. package/dist/core/runner/run-recovery-lanes.js +5 -3
  217. package/dist/core/runner/run-settle-and-teardown.js +2 -0
  218. package/dist/core/runner/run-terminal-adoption.js +21 -9
  219. package/dist/core/runner/run-turn-boundary.js +9 -3
  220. package/dist/core/runner/runtask.js +1 -1
  221. package/dist/core/runner/session-file-state-replay.d.ts +5 -2
  222. package/dist/core/runner/session-file-state-replay.js +3 -2
  223. package/dist/core/runner/skills-seat.d.ts +15 -0
  224. package/dist/core/runner/skills-seat.js +57 -0
  225. package/dist/core/runner/stream-steer-verb.js +2 -2
  226. package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
  227. package/dist/core/runner/sub-agent-deny-lists.js +7 -5
  228. package/dist/core/runner/synthetic-tools.d.ts +5 -2
  229. package/dist/core/runner/synthetic-tools.js +2 -12
  230. package/dist/core/runner/tool-face-overlay.d.ts +24 -6
  231. package/dist/core/runner/tool-face-overlay.js +24 -15
  232. package/dist/core/runner-deps.d.ts +37 -22
  233. package/dist/core/session-policy-store.d.ts +19 -0
  234. package/dist/core/session-policy-store.js +22 -2
  235. package/dist/core/session-store.d.ts +35 -16
  236. package/dist/core/session-store.js +20 -12
  237. package/dist/core/{shell-lexer.d.ts → shell-run-candidates.d.ts} +36 -12
  238. package/dist/core/{shell-lexer.js → shell-run-candidates.js} +145 -225
  239. package/dist/core/shell-wrapper-table.d.ts +5 -3
  240. package/dist/core/shell-wrapper-table.js +1 -1
  241. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  242. package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
  243. package/dist/core/store-contracts/session-store-contract.d.ts +49 -0
  244. package/dist/core/store-contracts/session-store-contract.js +639 -0
  245. package/dist/core/store-contracts/session-store-incarnation-contract.d.ts +12 -0
  246. package/dist/core/store-contracts/session-store-incarnation-contract.js +5 -2
  247. package/dist/core/task-event.d.ts +44 -6
  248. package/dist/core/task-notification.js +2 -2
  249. package/dist/core/task-registry-agent.js +1 -0
  250. package/dist/core/task-registry-shared.d.ts +5 -0
  251. package/dist/core/task-registry.d.ts +7 -0
  252. package/dist/core/task-registry.js +17 -0
  253. package/dist/core/task-result.d.ts +1 -1
  254. package/dist/core/task-spec.d.ts +7 -7
  255. package/dist/core/tighten-task-spec.d.ts +6 -4
  256. package/dist/core/tighten-task-spec.js +2 -2
  257. package/dist/core/tool-catalog-entries.d.ts +1 -1
  258. package/dist/core/tool-catalog-entries.js +52 -52
  259. package/dist/core/tool-face-reader.d.ts +49 -0
  260. package/dist/core/tool-face-reader.js +94 -0
  261. package/dist/core/tool-face.d.ts +17 -8
  262. package/dist/core/tool-face.js +1 -0
  263. package/dist/core/tool-policy.d.ts +67 -23
  264. package/dist/core/tool-policy.js +72 -60
  265. package/dist/core/tool-roster.d.ts +46 -28
  266. package/dist/core/tool-roster.js +91 -14
  267. package/dist/core/tool-spec-members.js +2 -2
  268. package/dist/core/tool-spec.d.ts +20 -4
  269. package/dist/core/tools.js +2 -0
  270. package/dist/core/trace.d.ts +24 -2
  271. package/dist/core/types.d.ts +3 -3
  272. package/dist/core/types.js +1 -1
  273. package/dist/core/untrusted-text.d.ts +23 -0
  274. package/dist/core/untrusted-text.js +10 -10
  275. package/dist/core/user-attachment.d.ts +56 -2
  276. package/dist/core/user-attachment.js +139 -56
  277. package/dist/core/wiring-manifest.d.ts +4 -2
  278. package/dist/engine/compaction/compaction.d.ts +2 -2
  279. package/dist/engine/compaction/utils.d.ts +2 -2
  280. package/dist/engine/harness/agent-harness.js +2 -1
  281. package/dist/engine/harness/types.d.ts +10 -4
  282. package/dist/engine/llm/types.d.ts +7 -2
  283. package/dist/engine/llm/types.js +2 -1
  284. package/dist/engine/loop/agent-loop.d.ts +12 -1
  285. package/dist/engine/loop/agent-loop.js +17 -5
  286. package/dist/engine/loop/types.d.ts +7 -1
  287. package/dist/engine/session/repo-utils.d.ts +5 -0
  288. package/dist/engine/session/repo-utils.js +9 -0
  289. package/dist/engine/session/storage-base.js +2 -2
  290. package/dist/index.d.ts +37 -17
  291. package/dist/index.js +31 -14
  292. package/dist/internal/harness.d.ts +1 -0
  293. package/dist/internal/harness.js +1 -0
  294. package/dist/internal/llm.d.ts +1 -1
  295. package/dist/internal/llm.js +1 -1
  296. package/dist/orchestration/goal.d.ts +24 -1
  297. package/dist/orchestration/goal.js +3 -2
  298. package/dist/orchestration/run-workflow-tool.d.ts +3 -0
  299. package/dist/orchestration/run-workflow-tool.js +1 -0
  300. package/dist/orchestration/workflow-governance.d.ts +1 -1
  301. package/dist/orchestration/workflow.d.ts +3 -0
  302. package/dist/orchestration/workflow.js +2 -1
  303. package/dist/prompt-assembly/artifact-store.d.ts +44 -9
  304. package/dist/prompt-assembly/artifact-store.js +38 -12
  305. package/dist/prompt-assembly/artifact.d.ts +12 -11
  306. package/dist/prompt-assembly/artifact.js +52 -21
  307. package/dist/prompt-assembly/assemble.d.ts +2 -0
  308. package/dist/prompt-assembly/assemble.js +2 -1
  309. package/dist/prompt-assembly/catalog/index.js +2 -1
  310. package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
  311. package/dist/prompt-assembly/catalog/memory.js +6 -0
  312. package/dist/prompt-assembly/catalog/schema.d.ts +19 -2
  313. package/dist/prompt-assembly/catalog/schema.js +3 -0
  314. package/dist/prompt-assembly/composer.d.ts +2 -0
  315. package/dist/prompt-assembly/composer.js +1 -1
  316. package/dist/prompt-assembly/overlay.d.ts +21 -11
  317. package/dist/prompt-assembly/overlay.js +57 -19
  318. package/dist/prompt-assembly/select.d.ts +21 -6
  319. package/dist/prompt-assembly/select.js +41 -4
  320. package/dist/prompts/default.d.ts +1 -12
  321. package/dist/prompts/default.js +0 -3
  322. package/dist/shell/lex.d.ts +87 -8
  323. package/dist/shell/lex.js +283 -49
  324. package/dist/stores/file/index.d.ts +8 -6
  325. package/dist/stores/file/permission-rule-store.d.ts +1 -1
  326. package/dist/stores/file/permission-rule-store.js +2 -1
  327. package/dist/tools/artifact/artifact-tool.js +14 -3
  328. package/dist/tools/fs/bash-program-position.d.ts +3 -15
  329. package/dist/tools/fs/bash-program-position.js +3 -10
  330. package/dist/tools/fs/bash-readonly-classifier.js +2 -0
  331. package/dist/tools/fs/fs-bash.d.ts +3 -3
  332. package/dist/tools/fs/fs-bash.js +72 -101
  333. package/dist/tools/fs/fs-read.d.ts +12 -0
  334. package/dist/tools/fs/fs-read.js +10 -9
  335. package/dist/tools/fs/fs-shared.d.ts +27 -31
  336. package/dist/tools/fs/fs-shared.js +12 -15
  337. package/dist/tools/fs/fs-write.js +1 -1
  338. package/dist/tools/fs/index.js +2 -2
  339. package/dist/tools/fs/read-deny-home.d.ts +1 -1
  340. package/dist/tools/fs/read-deny-home.js +5 -4
  341. package/dist/tools/fs/read-deny.d.ts +16 -3
  342. package/dist/tools/fs/read-deny.js +12 -9
  343. package/dist/tools/fs/safety.d.ts +7 -6
  344. package/dist/tools/fs/shell-read-bindings.d.ts +5 -3
  345. package/dist/tools/fs/shell-read-bindings.js +15 -16
  346. package/dist/tools/fs/shell-read-device.js +1 -1
  347. package/dist/tools/fs/shell-read-plan.js +36 -47
  348. package/dist/tools/fs/shell-read-word.d.ts +1 -6
  349. package/dist/tools/fs/shell-read-word.js +2 -20
  350. package/dist/tools/fs/shell-reentry-text.js +1 -1
  351. package/dist/tools/web.d.ts +2 -2
  352. package/package.json +4 -1
  353. package/test/export-surface.snapshot.json +5651 -5457
  354. package/wire-changes/core@7.53.0.json +1968 -0
  355. package/wire-changes/core@7.54.0.json +2870 -0
  356. package/dist/core/shell-scan.d.ts +0 -60
  357. package/dist/core/shell-scan.js +0 -183
@@ -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
@@ -2938,6 +2930,7 @@ export interface ManualCompactRef {
2938
2930
  */
2939
2931
  export type EndOfTaskCompaction = {
2940
2932
  compacted: boolean;
2933
+ pass?: string;
2941
2934
  tokensBefore?: number;
2942
2935
  freedTokens?: number;
2943
2936
  triggerTokens?: number;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The device lane's cross-boundary WIRING for one leg: the tree's shared state ref, the session record that carries the state from
3
+ * one leg of a session to the next, and the gate seat whose ONE obligation reading the station and both exits share.
4
+ *
5
+ * WHERE THE STATE LIVES. The tree's state is one object, reached from the tree's opaque token (`RunInternals.crossBoundaryTree`,
6
+ * `ToolExecuteContext.crossBoundaryTreeForChildren`) through this module alone; each leg joins into it what it knows at prepare (its
7
+ * environment's declaration, its session's newest record, its posture) and every bit an exit sets lands there before the call
8
+ * runs. Each leg's session holds a record (`CROSS_BOUNDARY_RECORD_TYPE`) of the state its content was produced under — written only
9
+ * for an armed tree: at prepare and at an exit (before the call runs) when the state grew past what the session already says, and at
10
+ * a leg's end (before a suspend's row is put, when the leg settles) also when conversation content follows the newest record. A
11
+ * write that fails refuses its call (or its row) on a governed tree; a prepare's or a settle's, and any write on a tree that is not
12
+ * governed, mints `config.cross_boundary_record_unwritten`. A record covers the content before it: a leg that takes over a session
13
+ * whose content no record covers (none on the branch, or after the newest), on an armed tree, reads that content as off-target.
14
+ *
15
+ * THE OBLIGATION of a call is read off three things at the moment it is asked ({@link crossBoundaryObligation}): the legs the
16
+ * call travels (its roster row's declaration, or what the dispatching code gives per call), whether the environment the `env`
17
+ * leg reaches declares itself the target NOW, and the tree's state NOW.
18
+ */
19
+ import type { AgentTool } from "../../internal/harness.js";
20
+ import type { SessionTreeEntry } from "../../internal/harness-types.js";
21
+ import { type CrossBoundaryCheck, type CrossBoundaryPosture, type CrossBoundaryTreeToken } from "../cross-boundary-taint.js";
22
+ import { type ToolRoster } from "../tool-roster.js";
23
+ import type { RunnerDeps } from "../types.js";
24
+ /** A new tree with no bit set, and the token that names it — the one mint of a token. */
25
+ export declare function mintCrossBoundaryTree(): CrossBoundaryTreeToken;
26
+ /** The tree a leg joins: the one its handed token names, or a new one (a root leg) — synchronous, so the phases that hand the token
27
+ * down never wait on the session. A token the engine did not mint names no tree it can read: reported, and the leg's tree reads as
28
+ * the strictest state. */
29
+ export declare function joinedCrossBoundaryTree(handed: CrossBoundaryTreeToken | undefined, report: (message: string) => void): CrossBoundaryTreeToken;
30
+ /** One leg's cross-boundary wiring (see the module header). */
31
+ export interface CrossBoundaryLane {
32
+ /** The tree's token — the one the leg's children are handed. */
33
+ readonly token: CrossBoundaryTreeToken;
34
+ /** The station and the exits are ENGAGED on this leg: a posture is declared, the leg runs on a target, or its tree is governed —
35
+ * each fixed at prepare, so no live consumer flips between two readings mid-run. */
36
+ readonly stationEngaged: boolean;
37
+ /** The gate seat, minted once the leg's roster and tool instances exist; undefined on a leg whose station is not engaged. */
38
+ seat(view: CrossBoundaryLegView): CrossBoundaryCheck | undefined;
39
+ /** The seat minted by {@link seat}, for the resumed call's exit. */
40
+ readonly check: CrossBoundaryCheck | undefined;
41
+ /** Write the record the tree's state owes this leg's session: the new entry's id, or undefined when nothing was owed (or, on a tree
42
+ * that is not governed, the write failed and was reported); throws when it cannot be written on a governed tree. With `atLeaf` (a
43
+ * suspend: a leg's end), the record also covers the content after the newest one and is written only as the child of that leaf
44
+ * (the row then points at it). */
45
+ owe(atLeaf?: string): Promise<string | undefined>;
46
+ /** The tree is armed, so the session OWES the tree's record and a parked row says a reader must read it — on a tree that is not
47
+ * governed a failed write leaves the record missing (the notice says so), and the reader then reads the content after the newest
48
+ * record as off-target. */
49
+ recorded(): boolean;
50
+ /** The leg settles: the record that covers what it added is written, a failure reported loudly (the next leg then reads the
51
+ * content after the newest record as off-target). */
52
+ settle(): Promise<void>;
53
+ /** How many times this leg's record writes have waited on the session store — a station that judged before an exit reads again
54
+ * when the count moved across it (the exit's own form: judged again after a wait). */
55
+ storeWaits(): number;
56
+ }
57
+ /** What the seat reads per call: the live roster (the legs a row declares) and the mounted instances (a `"per_call"` resolver). */
58
+ export interface CrossBoundaryLegView {
59
+ readonly tools: readonly AgentTool[];
60
+ readonly roster: {
61
+ readonly current: ToolRoster | undefined;
62
+ };
63
+ }
64
+ export interface CrossBoundaryLaneInput {
65
+ /** The tree this leg joins ({@link joinedCrossBoundaryTree}), or none — a new tree. */
66
+ token?: CrossBoundaryTreeToken;
67
+ /** The posture door's reading (`undefined` = undeclared). */
68
+ posture: CrossBoundaryPosture | undefined;
69
+ /** The run fold: this leg's environment declares the target, or the row it resumes was minted on one. */
70
+ externalContentTargetActive: boolean;
71
+ /** The leg's own environment — read for its CURRENT declaration at each judgment. */
72
+ env: {
73
+ readonly externalContentTarget?: boolean;
74
+ };
75
+ session: {
76
+ getBranch(): Promise<ReadonlyArray<SessionTreeEntry>>;
77
+ getLeafId(): Promise<string | null>;
78
+ appendCustomEntry(customType: string, data?: unknown): Promise<string>;
79
+ };
80
+ resumeLeg: boolean;
81
+ sessionId: string;
82
+ runId: string;
83
+ hostTaskId: string;
84
+ deps: Pick<RunnerDeps, "onError" | "onNotice" | "tracer">;
85
+ }
86
+ /** The leg's lane: the tree ref joined with what this leg knows, the session's record read and (when the state grew) written. */
87
+ export declare function prepareCrossBoundaryLane(input: CrossBoundaryLaneInput): Promise<CrossBoundaryLane>;
@@ -0,0 +1,190 @@
1
+ import { BOUNDARY_LEGS, CROSS_BOUNDARY_RECORD_TYPE, CROSS_BOUNDARY_TAINT_BITS, STRICTEST_CROSS_BOUNDARY_TREE, crossBoundaryObligation, crossBoundaryPostureUndeclaredNotice, crossBoundaryRecordUnwrittenNotice, crossBoundaryRecordData, crossBoundaryTreeCovers, crossBoundaryTreeGoverned, joinCrossBoundaryTree, readCrossBoundaryRecord, } from "../cross-boundary-taint.js";
2
+ import { deliverEngineNotice } from "../engine-notice.js";
3
+ import { policyCannotAnswer } from "../real-approval.js";
4
+ import { thrownText } from "../thrown-value.js";
5
+ import { rosterEntryNamed } from "../tool-roster.js";
6
+ import { emitTrace } from "../trace.js";
7
+ const TREE_STATE = new WeakMap();
8
+ export function mintCrossBoundaryTree() {
9
+ const token = Object.freeze(Object.create(null));
10
+ TREE_STATE.set(token, { current: Object.freeze({}) });
11
+ return token;
12
+ }
13
+ export function joinedCrossBoundaryTree(handed, report) {
14
+ if (handed !== undefined && TREE_STATE.has(handed))
15
+ return handed;
16
+ const token = mintCrossBoundaryTree();
17
+ if (handed !== undefined) {
18
+ report("a cross-boundary tree token the engine did not mint was handed to this leg — it reads its tree as armed, observed and governed");
19
+ TREE_STATE.get(token).current = STRICTEST_CROSS_BOUNDARY_TREE;
20
+ }
21
+ return token;
22
+ }
23
+ const EVERY_LEG = Object.freeze([...BOUNDARY_LEGS]);
24
+ const bitsOf = (t) => CROSS_BOUNDARY_TAINT_BITS.filter((b) => t[b] === true);
25
+ function legsOfCall(view, legEnv, token, call, report) {
26
+ const roster = view.roster.current;
27
+ const row = roster !== undefined ? rosterEntryNamed(roster, call.toolName) : undefined;
28
+ if (row === undefined)
29
+ return { legs: EVERY_LEG, env: undefined };
30
+ if (row.boundaryLegs !== "per_call")
31
+ return { legs: row.boundaryLegs, env: legEnv };
32
+ const strictest = (why) => (report(`tool "${row.name}" declares its boundary legs per call, and its boundaryLegsFor ${why} — the call reads as every leg, off the target (the strictest reading)`), { legs: EVERY_LEG, env: undefined });
33
+ const resolve = view.tools.find((t) => t.name === row.name)?.boundaryLegsFor;
34
+ if (typeof resolve !== "function")
35
+ return strictest("is missing");
36
+ let given;
37
+ try {
38
+ given = resolve(call.args, { tree: token });
39
+ }
40
+ catch (err) {
41
+ return strictest(`threw (${thrownText(err)})`);
42
+ }
43
+ const { legs, env } = (typeof given === "object" && given !== null ? given : {});
44
+ if (!Array.isArray(legs) || !legs.every((l) => BOUNDARY_LEGS.some((b) => b === l)) || (env !== undefined && (typeof env !== "object" || env === null)))
45
+ return strictest("answered outside its type ({ legs: a list of boundary legs, env?: an environment })");
46
+ return { legs: legs, env: env ?? legEnv };
47
+ }
48
+ export async function prepareCrossBoundaryLane(input) {
49
+ const { posture, externalContentTargetActive, env, session, resumeLeg, sessionId, runId, hostTaskId, deps } = input;
50
+ const report = (message) => deps.onError?.(new Error(message), { phase: "config", sessionId });
51
+ const token = input.token ?? mintCrossBoundaryTree();
52
+ const ref = TREE_STATE.get(token);
53
+ if (ref === undefined)
54
+ throw new TypeError("prepareCrossBoundaryLane: the token names no tree (resolve it with joinedCrossBoundaryTree)");
55
+ let branch;
56
+ try {
57
+ branch = await session.getBranch();
58
+ }
59
+ catch (err) {
60
+ report(`the session's cross-boundary record could not be read (${thrownText(err)}) — this leg reads its tree as armed, observed and governed`);
61
+ }
62
+ const read = branch !== undefined ? readCrossBoundaryRecord(branch) : undefined;
63
+ if (read?.kind === "malformed")
64
+ report(`${read.why} — this leg reads its tree as armed, observed and governed`);
65
+ const fromRecord = read === undefined ? STRICTEST_CROSS_BOUNDARY_TREE : read.kind === "absent" ? {} : read.tree;
66
+ let recorded = read?.kind === "recorded" ? read.tree : {};
67
+ const own = { ...(externalContentTargetActive ? { targetExecutionArmed: true } : {}), ...(posture === "multi_tenant" ? { governed: true } : {}) };
68
+ let tree = joinCrossBoundaryTree(joinCrossBoundaryTree(ref.current, fromRecord), own);
69
+ if (tree.targetExecutionArmed === true && read !== undefined && read.kind !== "malformed" && read.uncoveredContent)
70
+ tree = joinCrossBoundaryTree(tree, { offTargetReadObserved: true });
71
+ ref.current = tree;
72
+ const stationEngaged = posture !== undefined || externalContentTargetActive || crossBoundaryTreeGoverned(tree);
73
+ const unwritten = (phase, err) => deliverEngineNotice(deps.onNotice, crossBoundaryRecordUnwrittenNotice({ sessionId, runId, phase, governed: crossBoundaryTreeGoverned(ref.current), cause: thrownText(err) }));
74
+ let writing = Promise.resolve();
75
+ let storeWaits = 0;
76
+ const write = (phase, atLeaf) => {
77
+ const next = writing.then(async () => {
78
+ let leafAt = atLeaf;
79
+ let cover = phase === "suspend" || phase === "settle";
80
+ let written;
81
+ for (;;) {
82
+ const state = ref.current;
83
+ if (state.targetExecutionArmed !== true)
84
+ return written;
85
+ const grown = !crossBoundaryTreeCovers(recorded, state);
86
+ if (!grown && !cover)
87
+ return written;
88
+ try {
89
+ if (!grown) {
90
+ storeWaits++;
91
+ const now = readCrossBoundaryRecord((await session.getBranch()));
92
+ if (now.kind !== "malformed" && !now.uncoveredContent) {
93
+ cover = false;
94
+ continue;
95
+ }
96
+ }
97
+ if (leafAt !== undefined) {
98
+ storeWaits++;
99
+ if ((await session.getLeafId()) !== leafAt)
100
+ throw new Error("the session moved past the row's leaf before its record could be written");
101
+ }
102
+ storeWaits++;
103
+ const entryId = await session.appendCustomEntry(CROSS_BOUNDARY_RECORD_TYPE, crossBoundaryRecordData(state));
104
+ if (typeof entryId !== "string" || entryId.length === 0)
105
+ throw new Error(`the session store answered the record's append with no entry id (${typeof entryId})`);
106
+ if (leafAt !== undefined && (storeWaits++, await session.getLeafId()) !== entryId)
107
+ throw new Error("the record the session store appended is not the session's new leaf");
108
+ recorded = state;
109
+ written = entryId;
110
+ if (leafAt !== undefined)
111
+ leafAt = entryId;
112
+ cover = false;
113
+ }
114
+ catch (err) {
115
+ if (crossBoundaryTreeGoverned(ref.current))
116
+ throw err;
117
+ unwritten(phase, err);
118
+ recorded = state;
119
+ return written;
120
+ }
121
+ }
122
+ });
123
+ writing = next.catch(() => undefined);
124
+ return next;
125
+ };
126
+ const owe = (atLeaf) => write(atLeaf !== undefined ? "suspend" : "exit", atLeaf);
127
+ const trace = (phase, call) => emitTrace(deps.tracer, () => ({ kind: "permission.cross_boundary_taint", version: 1, taskId: hostTaskId, runId, phase, taint: bitsOf(ref.current), governed: ref.current.governed === true, ...(call !== undefined ? { toolName: call.toolName, ...(call.toolCallId !== undefined ? { toolCallId: call.toolCallId } : {}) } : {}), ts: Date.now() }));
128
+ if (tree.targetExecutionArmed === true) {
129
+ trace(resumeLeg ? "resume" : "prepare");
130
+ if (posture === undefined && tree.governed !== true)
131
+ deliverEngineNotice(deps.onNotice, crossBoundaryPostureUndeclaredNotice({ sessionId, runId, tree }));
132
+ await write("prepare").catch((err) => unwritten("prepare", err));
133
+ }
134
+ let check;
135
+ const lane = {
136
+ token,
137
+ stationEngaged,
138
+ get check() {
139
+ return check;
140
+ },
141
+ seat(view) {
142
+ if (!stationEngaged)
143
+ return undefined;
144
+ const obligationOf = (call, say) => {
145
+ const { legs, env: reached } = legsOfCall(view, env, token, call, say);
146
+ try {
147
+ return crossBoundaryObligation(legs, reached?.externalContentTarget === true, ref.current);
148
+ }
149
+ catch (err) {
150
+ say(`the cross-boundary obligation of "${call.toolName}" could not be read (${thrownText(err)}) — the call reads as every leg, off the target`);
151
+ return crossBoundaryObligation(EVERY_LEG, false, ref.current);
152
+ }
153
+ };
154
+ const quiet = () => undefined;
155
+ check = {
156
+ obligation: (call) => obligationOf(call, report),
157
+ async exit(call, decidedBy) {
158
+ for (;;) {
159
+ const now = obligationOf(call, quiet);
160
+ if (now.mandate && policyCannotAnswer({ action: "ask", requiresRealApproval: true }, decidedBy)) {
161
+ return {
162
+ kind: "unconfirmed",
163
+ message: `tool "${call.toolName}" needs a person's confirmation: this run executes on a target device${ref.current.offTargetReadObserved === true ? " and has since let content from beyond it in, so a call that reaches beyond the device or back into it" : ", so a call that writes beyond the device"} is put to a person — this call was allowed by a configuration, not a person, and was not executed`,
164
+ };
165
+ }
166
+ if (now.record && ref.current.offTargetReadObserved !== true) {
167
+ ref.current = joinCrossBoundaryTree(ref.current, { offTargetReadObserved: true });
168
+ trace("observed", call);
169
+ }
170
+ const judged = ref.current;
171
+ try {
172
+ await owe();
173
+ }
174
+ catch (err) {
175
+ return { kind: "unrecorded", message: `tool "${call.toolName}" was not executed: the session's cross-boundary record could not be written before it ran (${thrownText(err)}) — on a governed tree a call that owes its record does not run without it; refused fail-closed` };
176
+ }
177
+ if (ref.current === judged)
178
+ return undefined;
179
+ }
180
+ },
181
+ };
182
+ return check;
183
+ },
184
+ owe,
185
+ storeWaits: () => storeWaits,
186
+ recorded: () => ref.current.targetExecutionArmed === true,
187
+ settle: () => write("settle").then(() => undefined, (err) => unwritten("settle", err)),
188
+ };
189
+ return lane;
190
+ }
@@ -121,9 +121,9 @@ export declare function mintDecideRecord(cp: Checkpoint, facts: HostDecisionFact
121
121
  }): GateOutcome;
122
122
  /**
123
123
  * Capture-time domain check for a decide outcome's TEXT payloads — the review lanes' `editedPlan` /
124
- * `reason` and the approval lane's deny `reason`. All are persisted (the recorded winner's payload,
125
- * compared when an `env_failed` reopen replays it) and all are fenced into the model-facing
126
- * continuation reminder. The declared wire type is a string; a value outside it reads one way in this
124
+ * `reason`, the approval lane's deny `reason` and its allow `feedback`. All are persisted (the recorded winner's payload,
125
+ * compared when an `env_failed` reopen replays it) and all reach the model-facing continuation (the plan and the
126
+ * notes fenced into its reminder, the feedback as the person's own words). The declared wire type is a string; a value outside it reads one way in this
127
127
  * process and another after a JSON round-trip, and reaches the reminder as whatever its stringification
128
128
  * happens to be. Worse, the pre-CAS tag-hygiene check and the fence BOTH reach the value through
129
129
  * `String.prototype.replace`: an object carrying its own `replace` is therefore read twice and may
@@ -133,7 +133,7 @@ export declare function mintDecideRecord(cp: Checkpoint, facts: HostDecisionFact
133
133
  * is named in the message only — `CheckpointError`'s structured `detail.field` is a closed set covering
134
134
  * the approval lane's payload names, and widening it is a wire-surface change of its own.
135
135
  */
136
- export declare function assertOutcomeText(value: string | undefined, field: "editedPlan" | "reason"): void;
136
+ export declare function assertOutcomeText(value: string | undefined, field: "editedPlan" | "reason" | "feedback"): void;
137
137
  /**
138
138
  * The continuation prompt for a resumed run. The vendored harness only exposes
139
139
  * `prompt(text)` (it always appends a user message — there is no "continue from tool results" entry), so
@@ -8,6 +8,7 @@ import { facetOf, isGateKind, isQuestionGate } from "../pause-registry.js";
8
8
  import { humanRefusalSentence, policyCannotAnswer, screenApproverAttribution } from "../tool-policy.js";
9
9
  import { mintGateOutcome } from "./gate-exit.js";
10
10
  import { delimitUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
11
+ import { writtenFeedback } from "./approval-feedback.js";
11
12
  import { engineCardOf } from "../tool-roster.js";
12
13
  export function resumeDecisionWasNegative(resume) {
13
14
  const o = resume.outcome;
@@ -52,7 +53,8 @@ export function sameWinner(incoming, persisted) {
52
53
  incoming.decision === persisted.decision &&
53
54
  deepJsonEqual(incoming.updatedInput, persisted.updatedInput) &&
54
55
  deepJsonEqual(incoming.answer, persisted.answer) &&
55
- (persisted.reason === undefined || incoming.reason === persisted.reason));
56
+ (persisted.reason === undefined || incoming.reason === persisted.reason) &&
57
+ (persisted.feedback === undefined || writtenFeedback(incoming.feedback) === writtenFeedback(persisted.feedback)));
56
58
  }
57
59
  export function pendingContentAskCallId(cp) {
58
60
  return cp.pendingAction.kind === "tool_approval" && engineCardOf(cp.pendingAction) === "question" ? cp.pendingAction.toolCallId : undefined;
@@ -118,12 +118,16 @@ export declare function createSettlementLedger(call: {
118
118
  */
119
119
  export declare function cloneObserverInput(input: unknown): unknown;
120
120
  /**
121
- * The input a gate exit decided on, as the call's record keeps it ({@link ToolGateResult.finalInput}): a deep,
122
- * FROZEN copy — never the live object the tool receives (a tool or a later layer writing into its own arguments
123
- * must not rewrite what the gate decided), and never writable by a reader; the deny observer gets a writable
124
- * {@link cloneObserverInput} of it. A graph `structuredClone` refuses (functions, handles) is kept as a shallow
125
- * copy frozen at its own top level only — its members are the live arguments' own, and freezing those would
126
- * freeze the call's input under the tool.
121
+ * The input a gate exit decided on, as the call's record keeps it ({@link ToolGateResult.finalInput}): a PLAIN-DATA PROJECTION of
122
+ * it, deep and FROZEN — never the live input (a tool, a later layer or the caller writing into its own arguments cannot rewrite
123
+ * what the gate decided), and never writable by a reader; the deny observer gets a writable {@link cloneObserverInput} of it. Read
124
+ * by the engine's one receipt ({@link RECORD_RECEIPT}), so nothing of the caller's runs: every record and list copied off its own
125
+ * enumerable data members, and every member that is not data replaced by a marker naming what it was (`[not recorded: …]`) — a
126
+ * Map, a Date, a class instance, a function or a bigint by a marker, never by reference; a symbol-keyed member's marker joins its
127
+ * container's list of them (`[not recorded: symbol-keyed members]`, under a key no data member holds). On an ALLOW exit too: the
128
+ * record is the projection while the call runs with the value itself — the gate's own copy (`ownArgs`: records and lists, a getter
129
+ * already read into data) recorded as it is save a bigint, the member closing a cycle and containers past the bound, which are
130
+ * markers. A value the receipt refuses whole (a Proxy, a function, a Map in place of the arguments) is recorded as its marker.
127
131
  */
128
132
  export declare function frozenInputSnapshot(input: unknown): unknown;
129
133
  /** What one `classic.PreToolUse` screening came to: an answer the receipt took (`undefined` = no opinion), or the
@@ -211,9 +215,10 @@ export interface GatePass {
211
215
  * behind the policy's answer could not land in between. Returns the gate result (a park's suspension or the
212
216
  * exit's record). Writer: the dispatch (`runToolGate`, which wires `runGateLanes`). Reader: the fold's tail. */
213
217
  readonly afterFold: (pass: GatePass) => Promise<ToolGateResult>;
214
- /** borrowed-mutable — the FINAL args, always the pass's own immutable value (`ownArgs`): the model's input as taken at
215
- * the entry, rewritten by the phase-1 hook, by the policy rewrite on the ask path and by the exits' rewrite fold.
216
- * Writers: the dispatch (phase 1), the lanes, the exits. */
218
+ /** borrowed-mutable — the FINAL args, the pass's own immutable value (`ownArgs`): the model's input as taken at the entry,
219
+ * rewritten by the phase-1 hook, by the policy rewrite on the ask path and by the exits' rewrite fold — save on a refusal
220
+ * of a value nothing took ({@link untakenValueExit}), where it holds that value as given and only the deny exit's receipt
221
+ * reads it. Writers: the dispatch (phase 1), the lanes, the exits. */
217
222
  currentInput: unknown;
218
223
  /** borrowed-mutable — the adjudication request. Minted over the pass's args so the record is whole from
219
224
  * the first station, and RE-MINTED by the adjudication station over the FINAL post-screen args (the
@@ -341,9 +346,24 @@ export declare function engineFailClosedExit(pass: GatePass, reason: string): Pr
341
346
  /** The refusal sentence for args the gate could not take into its own copy (`ownArgs` refused them), naming what. */
342
347
  export declare const unownedArgsMessage: (subject: string, reason: string) => string;
343
348
  /**
344
- * The model's args could not be taken into the pass's own copy (a function, a shared buffer, a typed array): nobody judged
345
- * them, so nothing may run them. The single deny exit (the observer fires), in the policy layer's word — the value is the
346
- * deployment's own (a host driving the gate; the runner's calls are always clonable).
349
+ * A value nothing took — the model's args the pass could not take into its own copy (`ownArgs`: a function, a shared buffer, a
350
+ * typed array; {@link unownedArgsMessage}), or a resolution's edit no approval can be bound to (`bindableArgsOf`) — is refused
351
+ * here, through the single deny exit (the observer fires): nobody judged it, so nothing may run it, and it is no args of the
352
+ * call, so no reading of the tool's runs over it. The refusal carries no judged bytes (no `updatedInput`): the pass holds the
353
+ * value as given, and the deny exit's one receipt records it (`finalInput`, its plain-data projection — one marker when the
354
+ * receipt refuses it whole: a Map, a Date, a class instance, a Proxy or a function in place of the arguments). In the refusing
355
+ * layer's word: the resolution's for an edit (the lanes mint its `presentation_failed` record), the policy layer's for the
356
+ * model's args ({@link unownedArgsExit}).
357
+ */
358
+ export declare function untakenValueExit(pass: GatePass, untaken: {
359
+ readonly value: unknown;
360
+ readonly message: string;
361
+ readonly deniedBy: Extract<DeniedBy, "policy" | "ask_resolution">;
362
+ }): Promise<ToolGateResult>;
363
+ /**
364
+ * The model's args could not be taken into the pass's own copy (a function, a shared buffer, a typed array): the dispatch seeded
365
+ * the pass with them as given, and they leave through the exit for a value nothing took, in the policy layer's word — the value is
366
+ * the deployment's own (a host driving the gate; the runner's calls are always clonable).
347
367
  */
348
368
  export declare function unownedArgsExit(pass: GatePass, reason: string): Promise<ToolGateResult>;
349
369
  /** The hook's OWN deny in phase 1. A PreToolUse hook deny does NOT fire `permissionDenied`