@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
@@ -8,17 +8,20 @@ import { formatHookFeedback } from "../hooks.js";
8
8
  import { classicEventBase, failedHandlerClause, screenUserPrompt } from "../mods/stations.js";
9
9
  import { buildAppliedHumanInputEvent, buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
10
10
  import { humanInputAttachmentsOf, readObjectiveAttachments, userAttachmentDeclarations } from "../user-attachment.js";
11
- import { uuidv7 } from "../../internal/harness.js";
11
+ import { readToolCallArguments, uuidv7 } from "../../internal/harness.js";
12
12
  import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "../permission-rule-org.js";
13
13
  import { applyPersistedTightening, disclosedRuleSet } from "../persisted-rule-arms.js";
14
- import { parkedSkillApprovalRefusal, skillRowRefusal } from "../skill-grant.js";
14
+ import { argsReadBy, stampArgsReading } from "../effective-path-target.js";
15
+ import { skillRowRefusal } from "../skill-grant.js";
16
+ import { judgeApprovalObject } from "../approval-object.js";
15
17
  import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
16
18
  import { computeCostMicroUsd } from "../pricing.js";
17
19
  import { mintSystemReminder } from "../reminder-mint.js";
18
20
  import { hasDestroy } from "../remote-env.js";
19
- import { advertisedWritableRootsSeat, attestationOf, exemptRootsSeat, humanRefusalSentence, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
21
+ import { advertisedWritableRootsSeat, attestationOf, exemptRootsSeat, humanRefusalSentence, licenseCovers, ownedReading, policyCannotAnswer, policyRefusalMessage, refuseOutOfContractDecision } from "../tool-policy.js";
22
+ import { failedJudgmentDeny } from "./policy-throw.js";
20
23
  import { pathDenialCardOf } from "../../tools/fs/safety.js";
21
- import { callFaceSeat, judgeParkedToolIdentity, parkedRowLiveFace, rosterEntryNamed } from "../tool-roster.js";
24
+ import { callFaceSeat, parkedRowLiveFace, rosterEntryNamed } from "../tool-roster.js";
22
25
  import { deliverEngineNotice } from "../types.js";
23
26
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
24
27
  import { errorCodeOf } from "./terminal-account.js";
@@ -26,6 +29,7 @@ import { DEFAULT_PRECALL_OUTPUT_TOKENS, platformLimitTerminal } from "./clock-an
26
29
  import { legContextOverlayOption } from "./compaction-call-options.js";
27
30
  import { mintOutputStyleReminder, owedPostureText } from "./compaction-restate.js";
28
31
  import { deepJsonEqual, resumeContinuation } from "./decide-continuation.js";
32
+ import { approvalWordsOf, owedApprovalWords, withApprovalWords } from "./approval-feedback.js";
29
33
  import { chainCannotJudge } from "./call-hook-stations.js";
30
34
  import { settleExecutionRecord } from "./execution-record.js";
31
35
  import { holdPrompt } from "./prompt-block.js";
@@ -62,6 +66,10 @@ export async function runLeg(input) {
62
66
  }
63
67
  if (resume) {
64
68
  const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
69
+ const approvalWords = approvalWordsOf(resume.outcome);
70
+ const approvalFrames = owedApprovalWords(approvalWords, uuidv7());
71
+ let approvalCommitted = false;
72
+ prepared.undeliveredContinuationRef.current.parkedSteers.push(...approvalFrames);
65
73
  const resumeWindowRetryAfterMs = !walltimeExhaustedResume && prepared.usageGovernance !== undefined ? await prepared.usageGovernance.check(Date.now()) : undefined;
66
74
  if (resumeWindowRetryAfterMs !== undefined) {
67
75
  const owesNothing = resume.outcome.gate === "resource_limit" &&
@@ -234,13 +242,15 @@ export async function runLeg(input) {
234
242
  ...ident(),
235
243
  });
236
244
  }
245
+ if (approvalWords !== undefined)
246
+ continuation = withApprovalWords(continuation, engineSegments, approvalWords);
237
247
  const pending = resume.cp.pendingAction;
238
248
  const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
239
249
  ? maybeHumanRejectionHalt({ gate: resume.outcome.gateOutcome, isDelegatedChild: prepared.hookIdentity.isDelegatedChild, toolName: pending.toolName, toolCallId: pending.toolCallId })
240
250
  : undefined;
241
251
  if (rejectionHalt !== undefined && pending.kind === "tool_approval")
242
252
  prepared.batchHaltRef.current = { rejectedToolCallId: pending.toolCallId };
243
- prepared.undeliveredContinuationRef.current = { parkedSteers: resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), orphanTasks };
253
+ prepared.undeliveredContinuationRef.current = { parkedSteers: [...resumeFrames.map(({ entry }) => entry).filter((e) => !withheldSteerIds.has(e.inputId)), ...approvalFrames], orphanTasks };
244
254
  if (!prepared.abortController.signal.aborted) {
245
255
  if (resume !== undefined)
246
256
  resume.decisionDelivered = true;
@@ -262,11 +272,22 @@ export async function runLeg(input) {
262
272
  const preview = redactThenCut(prepared.outputStyleFrame, 220);
263
273
  prepared.frameReceipts.pending.push({ source: "output_style", text: continuation, commit: () => void (queue.push({ type: "steering_injected", source: "output_style", preview, ...ident() }), (rs.attach.attachmentsInjected += 1)) });
264
274
  }
275
+ if (approvalFrames.length > 0) {
276
+ prepared.frameReceipts.pending.push({
277
+ source: "approval_feedback",
278
+ text: continuation,
279
+ commit: () => {
280
+ approvalCommitted = true;
281
+ const seat = prepared.undeliveredContinuationRef.current;
282
+ seat.parkedSteers = seat.parkedSteers.filter((f) => !approvalFrames.includes(f));
283
+ },
284
+ });
285
+ }
265
286
  final = await withBrainSinks(() => prepared.harness.prompt(continuation, {
266
287
  engineSegments,
267
288
  settleUnansweredToolCalls: rejectionHalt !== undefined ? { halt: rejectionHalt } : {},
268
289
  onDelivered: () => {
269
- prepared.undeliveredContinuationRef.current = { parkedSteers: [], orphanTasks: [] };
290
+ prepared.undeliveredContinuationRef.current = { parkedSteers: approvalCommitted ? [] : [...approvalFrames], orphanTasks: [] };
270
291
  for (const e of appliedEvents)
271
292
  queue.push(e);
272
293
  },
@@ -529,19 +550,6 @@ async function applyResumeDecision(runner, prepared, resume, emit, emitCommitted
529
550
  return;
530
551
  if (pendingAction.kind !== "tool_approval")
531
552
  return;
532
- if (resume.outcome.gate === "policy_ask" &&
533
- resume.outcome.decision === "allow" &&
534
- !prepared.tools.some((t) => t.name === pendingAction.toolName)) {
535
- const e = new Error(`the approved tool "${pendingAction.toolName}" is no longer available on resume — refusing to continue as if it ran; the checkpoint is reopened for a retry with the tool present`);
536
- e.code = "resume.tool_unavailable";
537
- throw e;
538
- }
539
- const liveRoster = prepared.toolRosterDeltas.current;
540
- if (pendingAction.kind === "tool_approval" && liveRoster !== undefined && judgeParkedToolIdentity(pendingAction, rosterEntryNamed(liveRoster, pendingAction.toolName)) === "mismatch") {
541
- const e = new Error(`the approved tool "${pendingAction.toolName}" on this leg is not the tool the approval was minted against (its contract / shape / revision differ) — refusing to execute a replacement wearing the name; the checkpoint is reopened for a retry with the original tool present`);
542
- e.code = "resume.tool_contract_mismatch";
543
- throw e;
544
- }
545
553
  await resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart);
546
554
  }
547
555
  async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted, onResolvedToolSuccess, onExecuteStart) {
@@ -549,13 +557,29 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
549
557
  const { outcome } = resume;
550
558
  if (pendingAction.kind !== "tool_approval" || outcome.gate !== "policy_ask")
551
559
  return;
560
+ const mounted = () => { const tool = prepared.tools.find((x) => x.name === pendingAction.toolName); const roster = prepared.toolRosterDeltas.current; return tool === undefined || roster === undefined ? {} : { tool, entry: rosterEntryNamed(roster, pendingAction.toolName) }; };
561
+ const reopen = (miss) => { throw Object.assign(new Error(miss.message), { code: miss.code }); };
562
+ const liveTool = mounted().tool;
563
+ const atSession = outcome.decision === "allow" ? judgeApprovalObject(pendingAction, { tool: mounted().entry }, "session") : "match";
564
+ if (atSession !== "match" && atSession.disposition === "reopen")
565
+ reopen(atSession);
552
566
  const decidedGate = outcome.gateOutcome;
553
567
  if (decidedGate === undefined) {
554
568
  throw new CheckpointError("checkpoint.invalid_outcome", "pending action reached the resolver with no settlement record on its decision (gateOutcome) — refusing to execute or settle a decision that carries no record");
555
569
  }
556
570
  const humanRewrite = wireOf("resume.questionArgsShown", pendingAction) === ASK_USER_QUESTION_TOOL_NAME ? undefined : outcome.updatedInput;
557
- const resolvedArgs = humanRewrite !== undefined ? humanRewrite : pendingAction.args;
558
- const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
571
+ const reading = liveTool === undefined ? undefined : ownedReading((a) => readToolCallArguments(liveTool, { type: "toolCall", id: pendingAction.toolCallId, name: pendingAction.toolName, arguments: {} }, a));
572
+ const beltFace = () => { const f = parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction); if (f !== undefined && reading !== undefined)
573
+ stampArgsReading(f, reading); return f; };
574
+ const tryRead = (v) => { try {
575
+ return { args: reading === undefined ? v : argsReadBy(reading, v) };
576
+ }
577
+ catch (error) {
578
+ return { args: v, unread: { error } };
579
+ } };
580
+ const decided = outcome.decision === "allow" ? tryRead(humanRewrite !== undefined ? humanRewrite : pendingAction.args) : { args: humanRewrite !== undefined ? humanRewrite : pendingAction.args };
581
+ const resolvedArgs = decided.args;
582
+ const pendingLabel = (() => { const l = liveTool?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
559
583
  const spelledName = pendingAction.names?.[0] ?? pendingAction.toolName;
560
584
  const occurrences = prepared.toolOccurrences;
561
585
  const callStart = { toolCallId: pendingAction.toolCallId, spelledName, wireName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs };
@@ -563,15 +587,26 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
563
587
  occurrences.gateRound(pendingAction.toolCallId, decidedGate, frozenInputSnapshot(resolvedArgs));
564
588
  let notExecuted = false;
565
589
  const resultRow = (text, isError) => withRowNotExecuted(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, text, isError), notExecuted);
566
- const settleEnd = async (isError, result, refusedBy) => {
590
+ const settleEnd = async (isError, result, refusedBy, neverRan) => {
567
591
  if (refusedBy !== undefined)
568
592
  occurrences.refuse(pendingAction.toolCallId, refusedBy);
569
- const settled = emitSettled(occurrences, callStart, { result, isError }, { push: emit, structured: prepared.structuredProjector, mediaByteCapBytes: runner.deps.mediaByteCapBytes });
593
+ const settled = emitSettled(occurrences, callStart, { result, isError, ...(neverRan ? { notExecuted: true } : {}) }, { push: emit, structured: prepared.structuredProjector, mediaByteCapBytes: runner.deps.mediaByteCapBytes });
570
594
  notExecuted = settled.notExecuted === true;
571
595
  const gate = settled.gate ?? decidedGate;
572
596
  const word = await resume.recordExecutionOutcome(gate);
573
597
  settleExecutionRecord(runner.deps.onNotice, word, { toolName: pendingAction.toolName, gate, sessionId: prepared.sessionId, runId: prepared.runId, scope: resume.cp.scope, ...(resume.cp.checkpointId !== undefined ? { checkpointId: resume.cp.checkpointId } : {}) });
574
598
  };
599
+ const settleUnreadable = async (error, which) => {
600
+ const marks = egressValue("same-principal", thrownMarksOf(error));
601
+ const text = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: the tool could not read ${which} — its argument preparation failed (${egressThrownText("same-principal", error)}). This approval is spent — the call has to be re-issued.`, prepared.reminderMark);
602
+ await settleEnd(true, { content: text, ...(marks !== undefined ? { details: marks } : {}) }, undefined, true);
603
+ emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
604
+ };
605
+ const vetoApproval = async (miss) => {
606
+ const text = formatHookFeedback(miss.message, prepared.reminderMark);
607
+ await settleEnd(true, { content: text, ...(miss.code !== undefined ? { details: { code: miss.code } } : {}) }, "policy");
608
+ emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
609
+ };
575
610
  if (outcome.decision === "deny") {
576
611
  const settlementKind = outcome.gateOutcome?.settlement?.kind;
577
612
  const reason = settlementKind === "policy_refused"
@@ -593,6 +628,10 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
593
628
  if (outcome.decision !== "allow") {
594
629
  throw new CheckpointError("checkpoint.invalid_outcome", `pending action reached the resolver with decision "${String(outcome.decision)}" — only "allow" executes and only "deny" injects a denial; refusing to execute`);
595
630
  }
631
+ if (atSession !== "match")
632
+ return vetoApproval(atSession);
633
+ if (decided.unread !== undefined)
634
+ return settleUnreadable(decided.unread.error, humanRewrite !== undefined ? "the arguments the decision edited it to" : "its arguments");
596
635
  const screened = await prepared.approvedCallScreen?.({ toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, args: structuredClone(resolvedArgs), decidedBy: outcome.hostDecision.decidedBy });
597
636
  if (screened !== undefined && "refused" in screened) {
598
637
  const hookDenial = formatHookFeedback(screened.refused, prepared.reminderMark);
@@ -600,10 +639,13 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
600
639
  emitCommitted(await prepared.session.appendMessage(resultRow(hookDenial, true)), "toolResult", pendingAction.toolCallId);
601
640
  return;
602
641
  }
603
- const executedArgs = humanRewrite !== undefined ? resolvedArgs : screened !== undefined ? screened.args : resolvedArgs;
604
- const unseen = !deepJsonEqual(executedArgs, pendingAction.args);
642
+ const rescreened = humanRewrite === undefined && screened !== undefined ? tryRead(screened.args) : undefined;
643
+ if (rescreened?.unread !== undefined)
644
+ return settleUnreadable(rescreened.unread.error, "the arguments a PreToolUse hook rewrote it to");
645
+ const executedArgs = rescreened !== undefined ? rescreened.args : resolvedArgs;
646
+ const unseen = !licenseCovers(pendingAction.boundInputHash, executedArgs);
605
647
  let beltReading;
606
- const beltReq = () => ({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)), budget: beltBudget, ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}), ...(prepared.handsRoot !== undefined ? { root: prepared.handsRoot } : {}), ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs), ...exemptRootsSeat(prepared.exemptRoots), ...(beltReading !== undefined ? { targetReading: beltReading } : {}) });
648
+ const beltReq = () => ({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(beltFace()), budget: beltBudget, ...(prepared.cwdRef !== undefined ? { cwd: prepared.cwdRef.current } : {}), ...(prepared.handsRoot !== undefined ? { root: prepared.handsRoot } : {}), ...advertisedWritableRootsSeat(prepared.advertisedWritableDirs), ...exemptRootsSeat(prepared.exemptRoots), ...(beltReading !== undefined ? { targetReading: beltReading } : {}) });
607
649
  const unjudgeable = unseen ? chainCannotJudge(resume.cp.state.inheritedGate) : undefined;
608
650
  if (unjudgeable !== undefined) {
609
651
  const chainDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: its input changed after the approval and the inherited ancestor chain cannot judge the changed input (${unjudgeable}). This approval is spent — the call has to be re-issued.`, prepared.reminderMark);
@@ -620,14 +662,20 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
620
662
  const card = pathDenialCardOf(attestationOf(d, { toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName })?.pathDenial);
621
663
  return card !== undefined ? { details: card } : {};
622
664
  };
665
+ const beltCheck = async (judge, seat) => { const signal = prepared.abortController.signal; try {
666
+ return await judge(signal);
667
+ }
668
+ catch (err) {
669
+ return failedJudgmentDeny(err, signal, (e) => runner.deps.onError?.(e, { phase: "hook", sessionId: prepared.sessionId }), seat, pendingAction);
670
+ } };
623
671
  const beltStamped = prepared.stampCallTarget?.(beltReq(), prepared.abortController.signal);
624
672
  if (beltStamped !== undefined)
625
673
  beltReading = (await beltStamped).targetReading;
626
674
  if (unseen && prepared.basePolicyForResumeEdit) {
627
- const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check(beltReq(), prepared.abortController.signal));
675
+ const rechecked = refuseOutOfContractDecision(await beltCheck((signal) => prepared.basePolicyForResumeEdit.check(beltReq(), signal), "the deployment's tool policy re-checking a resumed call"));
628
676
  const personUnanswered = policyCannotAnswer(rechecked, outcome.hostDecision.decidedBy);
629
677
  const beltUnsafe = rechecked.action === "deny" ||
630
- (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, executedArgs)) ||
678
+ (rechecked.updatedInput !== undefined && ((read) => read.unread !== undefined || !deepJsonEqual(read.args, executedArgs))(tryRead(rechecked.updatedInput))) ||
631
679
  personUnanswered;
632
680
  if (beltUnsafe) {
633
681
  const why = rechecked.action === "deny"
@@ -653,7 +701,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
653
701
  }
654
702
  if (prepared.permissionRuleOrg !== undefined) {
655
703
  const orgVerdict = prepared.permissionRuleOrg
656
- .adjudicate({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)) })
704
+ .adjudicate({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(beltFace()) })
657
705
  .catch(() => ({ status: "unavailable", disclosures: ["the org adjudication face threw on resume"] }));
658
706
  let orgWaitCancelled = false;
659
707
  const org = await settleOrgVerdictWithin(orgVerdict, { status: "unavailable", disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`] }, {
@@ -687,7 +735,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
687
735
  }
688
736
  }
689
737
  if (prepared.denyNarrowingPolicy) {
690
- const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check(beltReq(), prepared.abortController.signal));
738
+ const narrowed = refuseOutOfContractDecision(await beltCheck((signal) => prepared.denyNarrowingPolicy.check(beltReq(), signal), "the narrowing rules re-checking a resumed call"));
691
739
  if (narrowed.action === "deny") {
692
740
  const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by ${ruleSourcePhrase("narrowing")} and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`, prepared.reminderMark);
693
741
  await settleEnd(true, { content: narrowedDenial, ...denialCard(narrowed) }, "policy");
@@ -729,7 +777,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
729
777
  }
730
778
  }
731
779
  if (prepared.shellReadCheck !== undefined) {
732
- const readCall = { toolName: pendingAction.toolName, ...callFaceSeat(parkedRowLiveFace(prepared.toolRosterDeltas.current, pendingAction)) };
780
+ const readCall = { toolName: pendingAction.toolName, ...callFaceSeat(beltFace()) };
733
781
  const judged = prepared.shellReadCheck.judge(readCall, executedArgs, prepared.cwdRef?.current, prepared.abortController.signal, {
734
782
  toolCallId: pendingAction.toolCallId,
735
783
  ...(unseen ? { seat: "rejudge" } : {}),
@@ -753,18 +801,24 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
753
801
  reading.verdict.admit?.();
754
802
  }
755
803
  }
756
- const grantRefusal = parkedSkillApprovalRefusal(pendingAction, executedArgs, prepared.skillSpecs, SKILL_TOOL_NAME, "execution");
757
- if (grantRefusal !== undefined) {
758
- const grantDenial = formatHookFeedback(grantRefusal, prepared.reminderMark);
759
- await settleEnd(true, { content: grantDenial }, "policy");
760
- emitCommitted(await prepared.session.appendMessage(resultRow(grantDenial, true)), "toolResult", pendingAction.toolCallId);
761
- return;
762
- }
763
- const tool = prepared.tools.find((t) => t.name === pendingAction.toolName);
764
- if (!tool) {
765
- const e = new Error(`the approved tool "${pendingAction.toolName}" is no longer available on resume`);
766
- e.code = "resume.tool_unavailable";
767
- throw e;
804
+ let tool;
805
+ for (;;) {
806
+ const { targetReading: _belts, ...unread } = beltReq();
807
+ const targetReading = (await prepared.stampCallTarget?.(unread, prepared.abortController.signal))?.targetReading;
808
+ const atExecution = judgeApprovalObject(pendingAction, { tool: mounted().entry, args: executedArgs, skills: prepared.skillSpecs, skillToolName: SKILL_TOOL_NAME, ...(humanRewrite !== undefined ? { decided: resolvedArgs } : {}), targetReading }, "execution");
809
+ if (atExecution !== "match")
810
+ return atExecution.disposition === "reopen" ? reopen(atExecution) : vetoApproval(atExecution);
811
+ tool = mounted().tool;
812
+ const waited = prepared.crossBoundary?.storeWaits() ?? 0;
813
+ const crossBoundaryRefusal = await prepared.crossBoundary?.check?.exit({ toolName: pendingAction.toolName, args: executedArgs, toolCallId: pendingAction.toolCallId }, outcome.hostDecision.decidedBy);
814
+ if (crossBoundaryRefusal !== undefined) {
815
+ const text = formatHookFeedback(crossBoundaryRefusal.message, prepared.reminderMark);
816
+ await settleEnd(true, { content: text }, "policy");
817
+ emitCommitted(await prepared.session.appendMessage(resultRow(text, true)), "toolResult", pendingAction.toolCallId);
818
+ return;
819
+ }
820
+ if ((prepared.crossBoundary?.storeWaits() ?? 0) === waited)
821
+ break;
768
822
  }
769
823
  const args = executedArgs;
770
824
  screened?.admit();
@@ -2,7 +2,7 @@ import { compactionKindOf } from "../task-notification.js";
2
2
  import { planRejectionClears } from "../context-edit.js";
3
3
  import { rosterToolName } from "../tool-roster.js";
4
4
  import { landStaleResultClears } from "./prepare-context-lane.js";
5
- import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent } from "../auto-compaction.js";
5
+ import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent } from "../auto-compaction.js";
6
6
  import { emitTrace } from "../trace.js";
7
7
  import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
8
8
  import { rebaseCadenceWindows } from "./turn-attachments.js";
@@ -22,6 +22,7 @@ export function runRecoveryLanes(input) {
22
22
  if (callerStop.signal.aborted)
23
23
  return false;
24
24
  const passSignal = AbortSignal.any([prepared.abortController.signal, callerStop.signal, ...(turnSignal !== undefined ? [turnSignal] : [])]);
25
+ const passReport = compactionPassReport(queue, ident);
25
26
  try {
26
27
  const comp = await maybeCompact({
27
28
  session: prepared.session,
@@ -50,6 +51,7 @@ export function runRecoveryLanes(input) {
50
51
  ...compactionRestateOptions(prepared),
51
52
  ...windowSafetyOptions(prepared.harness.getModel()),
52
53
  ...compactionHookOptions(runner, prepared, "forced", passSignal),
54
+ onStarted: passReport.onStarted,
53
55
  });
54
56
  if (comp.compacted) {
55
57
  compactionBreaker.failures = 0;
@@ -95,13 +97,13 @@ export function runRecoveryLanes(input) {
95
97
  catch (err) {
96
98
  if (isCompactionManualCancel(err) || (turnSignal?.aborted === true && !prepared.abortController.signal.aborted)) {
97
99
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
98
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "forced", reason: "cancelled", ...ident() });
100
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "forced", reason: "cancelled", ...passReport.end, ...ident() });
99
101
  return false;
100
102
  }
101
103
  compactionBreaker.failures += 1;
102
104
  runner.deps.onError?.(asThrownError(err), { phase: "compaction", sessionId: prepared.sessionId });
103
105
  const msg = thrownText(err);
104
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...ident() });
106
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "forced", reason: msg.length > 512 ? `${msg.slice(0, 512)}…` : msg, ...passReport.end, ...ident() });
105
107
  return false;
106
108
  }
107
109
  };
@@ -282,6 +282,8 @@ export async function runSettleAndTeardown(input) {
282
282
  const writeDateAnchor = owedDateAnchor(prepared.session, prepared.dateChange, rs.attach.dateState);
283
283
  if (writeDateAnchor !== undefined)
284
284
  await settleTeardownLeg(writeDateAnchor, "date anchor (settle leg)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
285
+ if (prepared.crossBoundary !== undefined)
286
+ await prepared.crossBoundary.settle();
285
287
  const ownedEnvDying = prepared.ownedEnv !== undefined &&
286
288
  hasDestroy(prepared.ownedEnv) &&
287
289
  isIsolated(prepared.ownedEnv) &&
@@ -1,4 +1,4 @@
1
- import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact } from "../auto-compaction.js";
1
+ import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact } from "../auto-compaction.js";
2
2
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
3
3
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
4
4
  import { emitTrace } from "../trace.js";
@@ -10,6 +10,7 @@ import { compactionHookOptions, recordCompactionReuse, seamCCompactionOptions }
10
10
  import { compactionRestateOptions } from "./compaction-restate.js";
11
11
  import { MAX_CONSECUTIVE_COMPACTION_FAILURES } from "./compaction-knobs.js";
12
12
  import { emitSettled, reconciledEnd } from "./tool-end-body.js";
13
+ import { isApprovalWordsFrame } from "./approval-feedback.js";
13
14
  import { asThrownError, thrownText } from "../thrown-value.js";
14
15
  export async function runTerminalAdoption(input) {
15
16
  const { spec, queue, prepared, rs, stats, ident, emitCommitted, startedToolCallIds, toolLabels, timeout, compactionBrain, compactionBreaker, windowSafetyOptions, undrainedUserAtEnd, userInputLostAtEnd, final, abortedLive, userInterruptedLive, callerStop, runner, sessions, next } = input;
@@ -78,21 +79,28 @@ export async function runTerminalAdoption(input) {
78
79
  }
79
80
  let migratedParked = { steer: 0, followUp: 0 };
80
81
  let carriedContinuation = 0;
82
+ let carriedApproval = 0;
83
+ let approvalRefusal;
81
84
  if (committedPause !== undefined && runner.deps.checkpointStore !== undefined) {
82
85
  const parkToken = committedPause.cause.token;
83
86
  const parkScope = committedPause.scope;
84
87
  const store = runner.deps.checkpointStore;
85
88
  const carried = [];
86
89
  const owed = [
87
- ...prepared.undeliveredContinuationRef.current.parkedSteers.map(({ seq: _seq, ...input }) => ({ input })),
90
+ ...prepared.undeliveredContinuationRef.current.parkedSteers.map((frame) => {
91
+ const { seq: _seq, ...input } = frame;
92
+ return { input, ...(isApprovalWordsFrame(frame) ? { approval: true } : {}) };
93
+ }),
88
94
  ...prepared.harness.readParkableUserInputs().map((record) => ({ input: record, live: record })),
89
95
  ];
90
- for (const { input, live } of owed) {
96
+ for (const { input, live, approval } of owed) {
91
97
  try {
92
98
  if (!(await store.setPendingSteer(parkToken, parkScope, input)))
93
99
  break;
94
100
  if (live !== undefined)
95
101
  carried.push(live);
102
+ else if (approval)
103
+ carriedApproval += 1;
96
104
  else
97
105
  carriedContinuation += 1;
98
106
  }
@@ -102,14 +110,17 @@ export async function runTerminalAdoption(input) {
102
110
  sessionId: prepared.sessionId,
103
111
  });
104
112
  const parkCode = errorCodeOf(parkErr);
105
- if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full")
113
+ if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full") {
114
+ if (approval)
115
+ approvalRefusal = parkCode;
106
116
  continue;
117
+ }
107
118
  break;
108
119
  }
109
120
  }
110
121
  migratedParked = prepared.harness.dropParkedUserInputs(carried);
111
122
  }
112
- const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers, carried: carriedContinuation } };
123
+ const owedUserInput = { held: undrainedUserAtEnd.current, migrated: migratedParked, continuation: { frames: prepared.undeliveredContinuationRef.current.parkedSteers.filter((f) => !isApprovalWordsFrame(f)), carried: carriedContinuation, approval: { owed: prepared.undeliveredContinuationRef.current.parkedSteers.filter(isApprovalWordsFrame).length, carried: carriedApproval, ...(approvalRefusal !== undefined ? { refusal: approvalRefusal } : {}) } } };
113
124
  if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
114
125
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
115
126
  }
@@ -120,7 +131,8 @@ export async function runTerminalAdoption(input) {
120
131
  minTokens: rs.counters.compactionFloor,
121
132
  brain: compactionBrain,
122
133
  windowSafety: windowSafetyOptions(prepared.harness.getModel()),
123
- onCompactionOutcome: (outcome, reason) => queue.push({ type: "compaction_outcome", outcome, trigger: "auto", reason, ...ident() }),
134
+ passReport: compactionPassReport(queue, ident),
135
+ onCompactionOutcome: (outcome, reason, end) => queue.push({ type: "compaction_outcome", outcome, trigger: "auto", reason, ...end, ...ident() }),
124
136
  }, runner, sessions);
125
137
  return await next({ threw, comp, answerSettled: settled, owedUserInput });
126
138
  }
@@ -153,7 +165,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
153
165
  ...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
154
166
  ...seamCCompactionOptions(runner, prepared),
155
167
  ...compactionRestateOptions(prepared),
156
- ...compactionHookOptions(runner, prepared, "auto", opts.callerStop.signal),
168
+ ...compactionHookOptions(runner, prepared, "auto", opts.callerStop.signal), onStarted: opts.passReport.onStarted,
157
169
  });
158
170
  recordCompactionReuse(runner, prepared, finishComp);
159
171
  if (finishComp.unevaluableWindow) {
@@ -170,7 +182,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
170
182
  reason: "cancelled",
171
183
  ts: Date.now(),
172
184
  }));
173
- opts?.onCompactionOutcome?.("mooted", "cancelled");
185
+ opts?.onCompactionOutcome?.("mooted", "cancelled", opts.passReport.end);
174
186
  }
175
187
  else {
176
188
  const failMsg = thrownText(err);
@@ -183,7 +195,7 @@ async function finish(spec, prepared, opts, runner, sessions) {
183
195
  reason: failReason,
184
196
  ts: Date.now(),
185
197
  }));
186
- opts?.onCompactionOutcome?.("failed", failReason);
198
+ opts?.onCompactionOutcome?.("failed", failReason, opts.passReport.end);
187
199
  runner.deps.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
188
200
  }
189
201
  }
@@ -1,7 +1,7 @@
1
1
  import { compactionKindOf } from "../task-notification.js";
2
2
  import { mintSystemReminder } from "../reminder-mint.js";
3
3
  import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
4
- import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
4
+ import { compactionPassReport, DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, mintCompactedEvent, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
5
5
  import { emitTrace } from "../trace.js";
6
6
  import { redactThenCut } from "../../agents/subagent-steps.js";
7
7
  import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, isIntactSource, rebaseCadenceWindows, renderAgentListingFrame, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
@@ -558,6 +558,7 @@ export function createTurnBoundary(input) {
558
558
  const passTrigger = trimPressure ? "forced" : forceManual ? "manual" : "auto";
559
559
  let manualOutcome = "failed";
560
560
  const manualInstructions = claimedManual?.instructions;
561
+ const passReport = compactionPassReport(queue, ident);
561
562
  try {
562
563
  const comp = await maybeCompact({
563
564
  session: prepared.session,
@@ -587,6 +588,7 @@ export function createTurnBoundary(input) {
587
588
  ...compactionRestateOptions(prepared),
588
589
  ...windowSafetyOptions(event.model),
589
590
  ...compactionHookOptions(runner, prepared, passTrigger, compactionPassSignal),
591
+ onStarted: passReport.onStarted,
590
592
  });
591
593
  if (comp.blocked) {
592
594
  emitTrace(rs.telemetry.tracer, () => ({
@@ -670,7 +672,7 @@ export function createTurnBoundary(input) {
670
672
  catch (err) {
671
673
  if (isCompactionManualCancel(err)) {
672
674
  emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "cancelled", ts: Date.now() }));
673
- queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...ident() });
675
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "cancelled", ...passReport.end, ...ident() });
674
676
  manualOutcome = "mooted";
675
677
  }
676
678
  else if (!prepared.abortController.signal.aborted) {
@@ -686,11 +688,15 @@ export function createTurnBoundary(input) {
686
688
  reason: failReason,
687
689
  ts: Date.now(),
688
690
  }));
689
- queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...ident() });
691
+ queue.push({ type: "compaction_outcome", outcome: "failed", trigger: passTrigger, reason: failReason, ...passReport.end, ...ident() });
690
692
  manualOutcome = "failed";
691
693
  }
692
694
  else {
693
695
  manualOutcome = "mooted";
696
+ if (passReport.pass !== undefined) {
697
+ emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.mooted", version: 1, taskId: rs.telemetry.taskId, reason: "task_ending", ts: Date.now() }));
698
+ queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: passTrigger, reason: "task_ending", ...passReport.end, ...ident() });
699
+ }
694
700
  }
695
701
  }
696
702
  if (forceManual)
@@ -481,7 +481,7 @@ export class Runner {
481
481
  outcome,
482
482
  taskConfig,
483
483
  internals,
484
- runner: this.depsSeat,
484
+ runner: this.depsSeat, sessions: this.sessions,
485
485
  parentConstraintRegistry: this.parentConstraintRegistry,
486
486
  next: (admitted) => this.resumeLadder(token, outcome, admitted),
487
487
  });
@@ -41,10 +41,13 @@ export type TranscriptFileRecord = {
41
41
  content: string;
42
42
  /** The recording message's own timestamp — the most honest "last read at" available. */
43
43
  at: number;
44
+ /** A Read result's call id — the transmission that carried the text (a Write's or an Edit's post-condition: none). */ transmittedByToolCallId?: string;
44
45
  } | {
45
46
  path: string;
46
- /** The entry the delivery committed. */
47
- observation: UserAttachmentObservation["entry"];
47
+ /** The entry the delivery committed, stamped with the frame that carried it (a trim that drops the frame retires it). */
48
+ observation: UserAttachmentObservation["entry"] & {
49
+ transmittedByToolCallId: string;
50
+ };
48
51
  at: number;
49
52
  };
50
53
  /**
@@ -3,6 +3,7 @@ import { isAbsoluteForFamily, pathFamilyOf } from "../../tools/fs/safety.js";
3
3
  import { applyRecordedEdit } from "../../tools/fs/fs-shared.js";
4
4
  import { normalizeFileText } from "../../tools/fs/encoding.js";
5
5
  import { isOffloadedDetailReplacement } from "../tool-result-store.js";
6
+ import { attachmentTransmissionId } from "../user-attachment.js";
6
7
  const READ_TOOL = "Read";
7
8
  const WRITE_TOOL = "Write";
8
9
  const EDIT_TOOL = "Edit";
@@ -69,7 +70,7 @@ export function wholeFileRecordsFromTranscript(messages) {
69
70
  if (attachment?.observation !== undefined) {
70
71
  const { key, entry } = attachment.observation;
71
72
  if (isAbsoluteForFamily(pathFamilyOf({ root: key }), key))
72
- byPath.set(key, { path: key, observation: entry, at: m.timestamp });
73
+ byPath.set(key, { path: key, observation: { ...entry, transmittedByToolCallId: attachmentTransmissionId(attachment) }, at: m.timestamp });
73
74
  continue;
74
75
  }
75
76
  if (m.role !== "toolResult" || m.isError)
@@ -112,7 +113,7 @@ export function wholeFileRecordsFromTranscript(messages) {
112
113
  continue;
113
114
  if (isOffloadedDetailReplacement(content))
114
115
  continue;
115
- byPath.set(filePath, { path: filePath, content, at: m.timestamp });
116
+ byPath.set(filePath, { path: filePath, content, at: m.timestamp, ...(isRead ? { transmittedByToolCallId: m.toolCallId } : {}) });
116
117
  }
117
118
  return [...byPath.values()];
118
119
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * THE reading of `TaskSpec.skills` — the one place the caller's skill objects are read. The config doors call it before any
3
+ * resource (a resume before its claim) and hand its copy on as the leg's `skills`; the disclosure block and the `Skill` tool read
4
+ * that copy through `normalizeSkills` (synthetic-tools.ts), its sorted view.
5
+ */
6
+ import type { SkillSpec } from "../agent-definition.js";
7
+ /**
8
+ * Total, and once: a value that is not a list, an entry that is not an object, a `name` / `description` / `content` that is not a
9
+ * string, or `files` that is not a list of `{ path, content }` strings refuses by name (`config.invalid_argument`, `detail.field` =
10
+ * the member's path); `baseDir` by its own reader ({@link skillBaseDirOf}, `config.skill_base_dir_invalid`). Every entry is
11
+ * screened, a later duplicate of a name included. Each field is read ONCE, BY NAME (an inherited or non-enumerable list counts;
12
+ * a rest copy would drop it). What it returns is plain data in declaration order, first of a name: every member and attachment
13
+ * copied, the two tables read ({@link readSkillTable}: a non-list is one entry that reads as nothing).
14
+ */
15
+ export declare function readSkillsSeat(skills: unknown): SkillSpec[];
@@ -0,0 +1,57 @@
1
+ import { readSkillTable } from "../skill-grant.js";
2
+ import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "../retired-keys.js";
3
+ import { skillBaseDirOf } from "./skill-base-dir.js";
4
+ const SKILLS_SEAT = "TaskSpec.skills";
5
+ const kindOf = (v) => (v === null ? "null" : Array.isArray(v) ? "an array" : typeof v);
6
+ function skillsSeatRefusal(field, problem) {
7
+ const e = new Error(`${field} ${problem} — an entry the engine cannot read is refused rather than dropped.`);
8
+ e.code = "config.invalid_argument";
9
+ e.detail = { field };
10
+ return e;
11
+ }
12
+ function readSkillFiles(files, at) {
13
+ if (files === undefined)
14
+ return undefined;
15
+ if (!Array.isArray(files))
16
+ throw skillsSeatRefusal(at, `must be a list of { path, content } attachments (got ${kindOf(files)})`);
17
+ const out = [];
18
+ for (const [j, f] of files.entries()) {
19
+ if (typeof f !== "object" || f === null || Array.isArray(f))
20
+ throw skillsSeatRefusal(`${at}[${j}]`, `must be an attachment object { path, content } (got ${kindOf(f)})`);
21
+ const { path, content } = f;
22
+ if (typeof path !== "string")
23
+ throw skillsSeatRefusal(`${at}[${j}].path`, `must be a string (got ${kindOf(path)})`);
24
+ if (typeof content !== "string")
25
+ throw skillsSeatRefusal(`${at}[${j}].content`, `must be a string (got ${kindOf(content)})`);
26
+ out.push({ path, content });
27
+ }
28
+ return out;
29
+ }
30
+ export function readSkillsSeat(skills) {
31
+ if (skills === undefined)
32
+ return [];
33
+ if (!Array.isArray(skills))
34
+ throw skillsSeatRefusal(SKILLS_SEAT, `must be a list (got ${kindOf(skills)})`);
35
+ assertNoRetiredKeys({ skills }, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
36
+ const seen = new Set();
37
+ const out = [];
38
+ for (const [i, raw] of skills.entries()) {
39
+ const at = `${SKILLS_SEAT}[${i}]`;
40
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
41
+ throw skillsSeatRefusal(at, `must be an object { name, description, content } (got ${kindOf(raw)})`);
42
+ const { name, description, content, allowedTools, disallowedTools, files: rawFiles, baseDir, ...rest } = raw;
43
+ void rest;
44
+ for (const [member, value] of [["name", name], ["description", description], ["content", content]]) {
45
+ if (typeof value !== "string")
46
+ throw skillsSeatRefusal(`${at}.${member}`, `must be a string (got ${kindOf(value)})`);
47
+ }
48
+ const files = readSkillFiles(rawFiles, `${at}.files`);
49
+ skillBaseDirOf(baseDir);
50
+ if (seen.has(name))
51
+ continue;
52
+ seen.add(name);
53
+ const [allowed, denied] = [readSkillTable(allowedTools), readSkillTable(disallowedTools)];
54
+ out.push({ name, description, content, ...(allowed !== undefined ? { allowedTools: allowed } : {}), ...(denied !== undefined ? { disallowedTools: denied } : {}), ...(files !== undefined ? { files } : {}), ...(baseDir !== undefined ? { baseDir } : {}) });
55
+ }
56
+ return out;
57
+ }