@sema-agent/server 7.147.0 → 7.148.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 (725) hide show
  1. package/MIGRATION.md +1039 -24
  2. package/README.md +20 -7
  3. package/README.zh-CN.md +14 -8
  4. package/USAGE.md +380 -163
  5. package/deploy/sema-up/README.md +16 -5
  6. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  7. package/deploy/sema-up/chart/templates/_helpers.tpl +20 -4
  8. package/deploy/sema-up/chart/values.yaml +3 -2
  9. package/deploy/sema-up/cluster-up.sh +74 -176
  10. package/deploy/sema-up/kube-up.sh +29 -49
  11. package/deploy/sema-up/lib/chart-run.sh +23 -0
  12. package/deploy/sema-up/lib/dataroot.sh +3 -3
  13. package/deploy/sema-up/lib/health.sh +62 -13
  14. package/deploy/sema-up/lib/helm-values-cluster.py +18 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +35 -0
  16. package/deploy/sema-up/lib/regcred.py +8 -0
  17. package/deploy/sema-up/lib/remote/helm-install.sh +3 -0
  18. package/deploy/sema-up/lib/remote/image-import.sh +6 -0
  19. package/deploy/sema-up/lib/remote/machine-precheck.sh +11 -0
  20. package/deploy/sema-up/lib/remote/region-probe.sh +3 -0
  21. package/deploy/sema-up/lib/remote/stack-ready.sh +26 -0
  22. package/deploy/sema-up/lib/remote/vxlan-guard.sh +8 -0
  23. package/deploy/sema-up/lib/render-stack.py +17 -0
  24. package/deploy/sema-up/lib/shell-text.sh +87 -0
  25. package/deploy/sema-up/lib/usage-flags.txt +10 -0
  26. package/deploy/sema-up/lib/usage-head.txt +1 -0
  27. package/deploy/sema-up/lib/usage-tail.txt +13 -0
  28. package/deploy/sema-up/preflight.sh +10 -8
  29. package/deploy/sema-up/sema-up.sh +14 -39
  30. package/deploy/sema-up/smoke.sh +7 -6
  31. package/deploy/sema-up/stack/compose.dataroot.yaml +3 -0
  32. package/deploy/sema-up/stack/compose.yaml +13 -1
  33. package/deploy/sema-up/stack/sema-stack-cluster.yaml +21 -0
  34. package/dist/adoption/plan.d.ts +24 -0
  35. package/dist/adoption/plan.js +53 -1
  36. package/dist/adoption/runner.d.ts +10 -1
  37. package/dist/adoption/runner.js +36 -16
  38. package/dist/adoption/wire.d.ts +3 -3
  39. package/dist/approval-card.d.ts +28 -30
  40. package/dist/approval-gate-currency.js +3 -2
  41. package/dist/approval-reconciler.d.ts +4 -13
  42. package/dist/approval-reconciler.js +2 -2
  43. package/dist/approval.d.ts +13 -11
  44. package/dist/approval.js +3 -3
  45. package/dist/audit.d.ts +10 -3
  46. package/dist/audit.js +15 -7
  47. package/dist/auth-bridge.d.ts +0 -14
  48. package/dist/auth-bridge.js +0 -7
  49. package/dist/auto-mode-face.d.ts +10 -7
  50. package/dist/auto-mode-face.js +7 -4
  51. package/dist/background-agent-name.d.ts +19 -1
  52. package/dist/background-agent-name.js +12 -2
  53. package/dist/background-session-reap.d.ts +1 -1
  54. package/dist/bake-runner/main.js +9 -6
  55. package/dist/bench/s1/arms.js +6 -5
  56. package/dist/bench/s1/live-deps.js +5 -2
  57. package/dist/bench/s1/run-firm.js +6 -5
  58. package/dist/bind-host.d.ts +25 -0
  59. package/dist/bind-host.js +25 -0
  60. package/dist/boot/adoption.d.ts +3 -0
  61. package/dist/boot/adoption.js +1 -0
  62. package/dist/boot/deployment-mods.d.ts +1 -1
  63. package/dist/boot/execution-env.d.ts +17 -7
  64. package/dist/boot/execution-env.js +37 -31
  65. package/dist/boot/exit-boundary.d.ts +2 -2
  66. package/dist/boot/exit-boundary.js +6 -5
  67. package/dist/boot/fatal-exit.d.ts +3 -5
  68. package/dist/boot/fatal-exit.js +2 -2
  69. package/dist/boot/leader.d.ts +15 -0
  70. package/dist/boot/leader.js +17 -15
  71. package/dist/boot/memory-consolidation.d.ts +37 -29
  72. package/dist/boot/memory-consolidation.js +11 -5
  73. package/dist/boot/org-memory.js +1 -1
  74. package/dist/boot/parked-revive-gate.js +2 -2
  75. package/dist/boot/reapers.d.ts +4 -3
  76. package/dist/boot/reapers.js +19 -16
  77. package/dist/boot/resolve-spec.d.ts +5 -3
  78. package/dist/boot/resolve-spec.js +55 -67
  79. package/dist/boot/retention-lane.d.ts +4 -4
  80. package/dist/boot/retention-lane.js +2 -2
  81. package/dist/boot/runner-deps.d.ts +28 -7
  82. package/dist/boot/runner-deps.js +11 -4
  83. package/dist/boot/runtime-caps.d.ts +2 -2
  84. package/dist/boot/runtime-caps.js +16 -2
  85. package/dist/boot/session-faces.d.ts +16 -7
  86. package/dist/boot/session-faces.js +114 -94
  87. package/dist/boot/shutdown.d.ts +5 -4
  88. package/dist/boot/shutdown.js +6 -4
  89. package/dist/boot/stage-01-config.js +9 -1
  90. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  91. package/dist/boot/stage-02-web-admin-pull.js +6 -1
  92. package/dist/boot/stage-03-store-backend.d.ts +3 -0
  93. package/dist/boot/stage-03-store-backend.js +4 -3
  94. package/dist/boot/stage-04-stores.d.ts +2 -0
  95. package/dist/boot/stage-04-stores.js +2 -2
  96. package/dist/boot/stage-05-execution-env.d.ts +7 -3
  97. package/dist/boot/stage-05-execution-env.js +2 -2
  98. package/dist/boot/stage-06-runners.d.ts +10 -12
  99. package/dist/boot/stage-06-runners.js +23 -15
  100. package/dist/boot/stage-07-capability-layer.d.ts +11 -8
  101. package/dist/boot/stage-07-capability-layer.js +5 -3
  102. package/dist/boot/stage-08-reapers.d.ts +11 -8
  103. package/dist/boot/stage-08-reapers.js +3 -3
  104. package/dist/boot/stage-09-leader.d.ts +12 -15
  105. package/dist/boot/stage-09-leader.js +12 -4
  106. package/dist/boot/stage-10-http-server.d.ts +12 -15
  107. package/dist/boot/stage-10-http-server.js +7 -4
  108. package/dist/boot/store-evidence.d.ts +20 -0
  109. package/dist/boot/store-evidence.js +13 -0
  110. package/dist/boot/stores.d.ts +3 -0
  111. package/dist/boot/stores.js +21 -10
  112. package/dist/boot/task-list-lane.d.ts +4 -4
  113. package/dist/boot/web-admin.d.ts +4 -2
  114. package/dist/boot/web-admin.js +55 -35
  115. package/dist/boot/webfetch-summarize-lane.d.ts +1 -1
  116. package/dist/boot-reclaim.d.ts +3 -1
  117. package/dist/boot-reclaim.js +1 -1
  118. package/dist/brain.d.ts +7 -18
  119. package/dist/brain.js +5 -17
  120. package/dist/budget.js +6 -6
  121. package/dist/capabilities/center-plugins.d.ts +7 -1
  122. package/dist/capabilities/center-plugins.js +32 -10
  123. package/dist/capabilities/center-wiring.js +1 -1
  124. package/dist/capabilities/composite-members.d.ts +2 -2
  125. package/dist/capabilities/composite-members.js +2 -2
  126. package/dist/capabilities/repo-tools.d.ts +2 -0
  127. package/dist/capabilities/repo-tools.js +14 -12
  128. package/dist/capabilities/scenarios.d.ts +6 -15
  129. package/dist/capabilities/scenarios.js +11 -16
  130. package/dist/capabilities/skills.d.ts +1 -1
  131. package/dist/capabilities/tool-defer.d.ts +1 -1
  132. package/dist/capability-facts.d.ts +38 -11
  133. package/dist/capability-facts.js +47 -8
  134. package/dist/card-void.d.ts +4 -4
  135. package/dist/card-void.js +1 -1
  136. package/dist/center-credential-renewer.d.ts +6 -2
  137. package/dist/center-credential-renewer.js +7 -3
  138. package/dist/config-catalog.d.ts +16 -1
  139. package/dist/config-catalog.js +62 -56
  140. package/dist/config-dir-trust.d.ts +37 -4
  141. package/dist/config-dir-trust.js +88 -44
  142. package/dist/config-invariants.d.ts +42 -3
  143. package/dist/config-invariants.js +69 -5
  144. package/dist/config-lkg.d.ts +23 -1
  145. package/dist/config-lkg.js +17 -3
  146. package/dist/config-provider.d.ts +9 -9
  147. package/dist/config-provider.js +25 -110
  148. package/dist/config-pull-failure.d.ts +50 -0
  149. package/dist/config-pull-failure.js +48 -0
  150. package/dist/config-types.d.ts +26 -17
  151. package/dist/config.d.ts +8 -16
  152. package/dist/config.js +113 -47
  153. package/dist/delegated-session-owner.d.ts +1 -1
  154. package/dist/deleted-session-ids.d.ts +3 -2
  155. package/dist/deployment-governance.d.ts +3 -3
  156. package/dist/deployment-shape.d.ts +30 -4
  157. package/dist/deployment-shape.js +7 -0
  158. package/dist/egress-redirect.d.ts +25 -7
  159. package/dist/egress-redirect.js +20 -2
  160. package/dist/elicitation.d.ts +6 -5
  161. package/dist/elicitation.js +23 -22
  162. package/dist/engine-identity.d.ts +28 -0
  163. package/dist/engine-identity.js +10 -0
  164. package/dist/entry-problems.d.ts +5 -7
  165. package/dist/entry-problems.js +1 -1
  166. package/dist/env-facts.js +2 -1
  167. package/dist/env-witness.d.ts +7 -0
  168. package/dist/env-witness.js +16 -0
  169. package/dist/execution-lane-caps.d.ts +5 -18
  170. package/dist/execution-lane-caps.js +0 -22
  171. package/dist/execution-placement.d.ts +58 -18
  172. package/dist/execution-placement.js +41 -2
  173. package/dist/field-conflict.d.ts +7 -11
  174. package/dist/field-conflict.js +3 -3
  175. package/dist/file-history-capture.d.ts +3 -3
  176. package/dist/file-history-disclosure.js +1 -0
  177. package/dist/fleet/fleet-bus.d.ts +21 -3
  178. package/dist/fleet/fleet-bus.js +35 -25
  179. package/dist/fleet/fleet-reconciler.js +2 -2
  180. package/dist/fleet/serving-lease.js +3 -3
  181. package/dist/fleet-client.js +3 -3
  182. package/dist/fleet-lease.d.ts +0 -1
  183. package/dist/fleet-lease.js +4 -5
  184. package/dist/history-anchor.d.ts +23 -3
  185. package/dist/history-anchor.js +24 -4
  186. package/dist/hitl-delivery.d.ts +90 -0
  187. package/dist/hitl-delivery.js +98 -0
  188. package/dist/hooks/classic-stations.d.ts +8 -11
  189. package/dist/hooks/classic-stations.js +0 -7
  190. package/dist/hooks/hook-llm.d.ts +5 -1
  191. package/dist/hooks/hook-llm.js +24 -15
  192. package/dist/hooks/hook-runner.d.ts +67 -57
  193. package/dist/hooks/hook-runner.js +260 -186
  194. package/dist/host-lsp-manager.d.ts +22 -8
  195. package/dist/host-lsp-manager.js +4 -1
  196. package/dist/http/active-run-conflict.d.ts +2 -43
  197. package/dist/http/active-run-conflict.js +3 -7
  198. package/dist/http/admission.d.ts +4 -18
  199. package/dist/http/admission.js +75 -78
  200. package/dist/http/approval-sweeps.d.ts +3 -3
  201. package/dist/http/approval-sweeps.js +54 -30
  202. package/dist/http/caller-trust.d.ts +8 -10
  203. package/dist/http/caller-trust.js +1 -2
  204. package/dist/http/claim-heartbeat.d.ts +49 -0
  205. package/dist/http/claim-heartbeat.js +46 -0
  206. package/dist/http/claimed-run.d.ts +4 -3
  207. package/dist/http/dispatch.d.ts +8 -1
  208. package/dist/http/dispatch.js +76 -30
  209. package/dist/http/durable-run-leg.d.ts +3 -3
  210. package/dist/http/durable-run-leg.js +17 -12
  211. package/dist/http/execution-context.d.ts +17 -43
  212. package/dist/http/execution-context.js +7 -5
  213. package/dist/http/health-reader.d.ts +36 -0
  214. package/dist/http/health-reader.js +21 -0
  215. package/dist/http/health-self-report.d.ts +9 -9
  216. package/dist/http/health-self-report.js +4 -4
  217. package/dist/http/idempotency.d.ts +9 -5
  218. package/dist/http/idempotency.js +4 -4
  219. package/dist/http/memory-compliance-failure-reply.d.ts +15 -16
  220. package/dist/http/memory-compliance-failure-reply.js +4 -2
  221. package/dist/http/memory-face-fault-reply.d.ts +67 -0
  222. package/dist/http/memory-face-fault-reply.js +71 -0
  223. package/dist/http/memory-store-refusal-reply.d.ts +1 -1
  224. package/dist/http/memory-store-refusal-reply.js +3 -0
  225. package/dist/http/owner-access.d.ts +391 -67
  226. package/dist/http/owner-access.js +371 -35
  227. package/dist/http/principal-gate.d.ts +24 -81
  228. package/dist/http/principal-gate.js +24 -68
  229. package/dist/http/refusal-body.d.ts +35 -2
  230. package/dist/http/refusal-body.js +34 -0
  231. package/dist/http/request-guards.d.ts +9 -13
  232. package/dist/http/request-guards.js +34 -68
  233. package/dist/http/resume-composition.d.ts +37 -0
  234. package/dist/http/resume-composition.js +18 -0
  235. package/dist/http/resume-ctx-read.d.ts +35 -12
  236. package/dist/http/resume-ctx-read.js +25 -6
  237. package/dist/http/resume-legs.d.ts +21 -18
  238. package/dist/http/resume-legs.js +189 -129
  239. package/dist/http/retired-skill-manifest.d.ts +1 -1
  240. package/dist/http/route-ctx.d.ts +25 -32
  241. package/dist/http/routes/a2a-serve.d.ts +43 -5
  242. package/dist/http/routes/a2a-serve.js +58 -37
  243. package/dist/http/routes/admin-config-refresh.js +2 -2
  244. package/dist/http/routes/admin-drain.js +2 -2
  245. package/dist/http/routes/admin-memory-optout.js +4 -3
  246. package/dist/http/routes/adoption.d.ts +3 -0
  247. package/dist/http/routes/adoption.js +17 -10
  248. package/dist/http/routes/agents-roster.js +13 -7
  249. package/dist/http/routes/approvals-assistant.js +70 -65
  250. package/dist/http/routes/attachments.js +2 -2
  251. package/dist/http/routes/background.d.ts +3 -3
  252. package/dist/http/routes/background.js +9 -17
  253. package/dist/http/routes/capabilities.js +15 -18
  254. package/dist/http/routes/config-catalog.js +2 -2
  255. package/dist/http/routes/devices.d.ts +3 -3
  256. package/dist/http/routes/devices.js +4 -4
  257. package/dist/http/routes/diagnostics.d.ts +27 -3
  258. package/dist/http/routes/diagnostics.js +27 -21
  259. package/dist/http/routes/engine.js +2 -2
  260. package/dist/http/routes/fleet.js +5 -3
  261. package/dist/http/routes/images.js +10 -6
  262. package/dist/http/routes/leader.d.ts +7 -2
  263. package/dist/http/routes/leader.js +12 -3
  264. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  265. package/dist/http/routes/mcp-reconnect.js +7 -15
  266. package/dist/http/routes/memory-bundle.js +4 -7
  267. package/dist/http/routes/memory-compliance.js +4 -4
  268. package/dist/http/routes/memory-consolidation.js +6 -9
  269. package/dist/http/routes/memory-origin.d.ts +2 -0
  270. package/dist/http/routes/memory-origin.js +7 -8
  271. package/dist/http/routes/memory-policy.js +17 -12
  272. package/dist/http/routes/notify-wake.js +10 -19
  273. package/dist/http/routes/observability.js +3 -3
  274. package/dist/http/routes/principal-inbox.d.ts +1 -1
  275. package/dist/http/routes/principal-inbox.js +2 -2
  276. package/dist/http/routes/projects.d.ts +1 -1
  277. package/dist/http/routes/projects.js +3 -2
  278. package/dist/http/routes/retention-ops.js +6 -9
  279. package/dist/http/routes/rules.js +20 -5
  280. package/dist/http/routes/run-memory-capture.d.ts +2 -2
  281. package/dist/http/routes/run-memory-capture.js +6 -12
  282. package/dist/http/routes/run-permission-mode.d.ts +2 -2
  283. package/dist/http/routes/run-permission-mode.js +10 -17
  284. package/dist/http/routes/runs.js +146 -231
  285. package/dist/http/routes/session-memory-sync.d.ts +2 -2
  286. package/dist/http/routes/session-memory-sync.js +7 -13
  287. package/dist/http/routes/session-sync.d.ts +4 -2
  288. package/dist/http/routes/session-sync.js +21 -20
  289. package/dist/http/routes/sessions-list.d.ts +3 -3
  290. package/dist/http/routes/sessions-list.js +9 -9
  291. package/dist/http/routes/sessions.js +87 -124
  292. package/dist/http/routes/shared-memory.js +6 -6
  293. package/dist/http/routes/side-query.js +2 -2
  294. package/dist/http/routes/tasks.js +136 -120
  295. package/dist/http/routes/trace-usage.d.ts +0 -12
  296. package/dist/http/routes/trace-usage.js +40 -46
  297. package/dist/http/routes/workflows.d.ts +39 -4
  298. package/dist/http/routes/workflows.js +42 -14
  299. package/dist/http/run-concurrency-cap.d.ts +20 -12
  300. package/dist/http/run-concurrency-cap.js +39 -26
  301. package/dist/http/run-read-face.d.ts +3 -5
  302. package/dist/http/run-read-face.js +8 -13
  303. package/dist/http/send.d.ts +12 -3
  304. package/dist/http/send.js +42 -6
  305. package/dist/http/server.d.ts +39 -40
  306. package/dist/http/server.js +36 -11
  307. package/dist/http/sse-log.d.ts +17 -10
  308. package/dist/http/sse-log.js +7 -7
  309. package/dist/http/stored-body-replay.d.ts +12 -0
  310. package/dist/http/stored-body-replay.js +2 -2
  311. package/dist/http/submit-ctx.d.ts +16 -8
  312. package/dist/http/submit-ctx.js +6 -2
  313. package/dist/http/submit-replay.d.ts +19 -6
  314. package/dist/http/submit-replay.js +18 -6
  315. package/dist/http/sync-submit-body.d.ts +3 -4
  316. package/dist/http/sync-submit-body.js +4 -5
  317. package/dist/http/wire-types.d.ts +4 -3
  318. package/dist/identity-source.d.ts +36 -5
  319. package/dist/identity-source.js +27 -0
  320. package/dist/index.d.ts +1 -0
  321. package/dist/index.js +1 -0
  322. package/dist/key-resolver.d.ts +32 -0
  323. package/dist/key-resolver.js +23 -9
  324. package/dist/leader/diffup.js +2 -1
  325. package/dist/leader/endpoint.d.ts +26 -5
  326. package/dist/leader/endpoint.js +154 -14
  327. package/dist/leader/fanout.d.ts +3 -0
  328. package/dist/leader/fanout.js +78 -63
  329. package/dist/leader/leader.d.ts +16 -0
  330. package/dist/leader/leader.js +43 -3
  331. package/dist/leader/merge.d.ts +9 -1
  332. package/dist/leader/merge.js +16 -1
  333. package/dist/leader/repair-wire.js +1 -1
  334. package/dist/leader/wire.d.ts +36 -3
  335. package/dist/leader/wire.js +59 -18
  336. package/dist/lsp-evict.d.ts +6 -4
  337. package/dist/lsp-evict.js +2 -3
  338. package/dist/memory-operator-faces.d.ts +5 -4
  339. package/dist/memory-operator-faces.js +1 -1
  340. package/dist/memory-scope.d.ts +29 -2
  341. package/dist/memory-scope.js +64 -15
  342. package/dist/memory-store-refusal.d.ts +5 -2
  343. package/dist/memory-sync-client.d.ts +3 -2
  344. package/dist/memory-sync-client.js +6 -3
  345. package/dist/model-compat.d.ts +17 -8
  346. package/dist/model-compat.js +6 -2
  347. package/dist/model-context-window.d.ts +18 -0
  348. package/dist/model-context-window.js +2 -0
  349. package/dist/model-entry-headers.d.ts +46 -4
  350. package/dist/model-entry-headers.js +2 -2
  351. package/dist/model-entry-refusal.d.ts +35 -16
  352. package/dist/model-entry-refusal.js +40 -27
  353. package/dist/model-plane-face.d.ts +19 -11
  354. package/dist/model-plane-face.js +9 -1
  355. package/dist/model-plane-probe.d.ts +2 -0
  356. package/dist/model-plane-probe.js +17 -0
  357. package/dist/model-provider.d.ts +3 -3
  358. package/dist/model-route-endpoint.d.ts +9 -5
  359. package/dist/model-route-endpoint.js +29 -7
  360. package/dist/model-select.d.ts +16 -0
  361. package/dist/model-select.js +4 -0
  362. package/dist/mountinfo.d.ts +37 -0
  363. package/dist/mountinfo.js +47 -0
  364. package/dist/namespaced-rule-name.d.ts +23 -14
  365. package/dist/namespaced-rule-name.js +8 -11
  366. package/dist/net-address.d.ts +1 -1
  367. package/dist/observability/corrupt-read-seat.d.ts +4 -4
  368. package/dist/observability/corrupt-read-seat.js +5 -3
  369. package/dist/observability/fail-open.d.ts +58 -38
  370. package/dist/observability/fail-open.js +60 -38
  371. package/dist/observability/logger.d.ts +22 -1
  372. package/dist/observability/logger.js +89 -12
  373. package/dist/observability/metrics.js +3 -4
  374. package/dist/observability/otel-exporter.js +2 -1
  375. package/dist/observability/principal-context.d.ts +14 -7
  376. package/dist/observability/principal-context.js +8 -3
  377. package/dist/observability/run-terminal.d.ts +7 -1
  378. package/dist/observability/run-terminal.js +5 -2
  379. package/dist/orchestration/hardened-vm-runner.d.ts +1 -0
  380. package/dist/orchestration/hardened-vm-runner.js +11 -5
  381. package/dist/orchestration/hardened-vm-worker-runner.d.ts +0 -14
  382. package/dist/orchestration/hardened-vm-worker-runner.js +5 -0
  383. package/dist/orchestration/hardened-vm-worker.js +2 -1
  384. package/dist/orchestration/workflow-completion-inbox.d.ts +3 -3
  385. package/dist/orchestration/workflow-completion-inbox.js +3 -2
  386. package/dist/org-delivery-gate.d.ts +5 -4
  387. package/dist/org-delivery-gate.js +7 -6
  388. package/dist/outbound-dispatcher.d.ts +1 -1
  389. package/dist/outbound-dispatcher.js +6 -12
  390. package/dist/owner-brands.d.ts +81 -0
  391. package/dist/owner-brands.js +42 -0
  392. package/dist/owner-faces.d.ts +531 -0
  393. package/dist/owner-faces.js +126 -0
  394. package/dist/parked-decide.d.ts +67 -5
  395. package/dist/parked-decide.js +127 -5
  396. package/dist/parked-revive-surface.d.ts +14 -4
  397. package/dist/parked-revive-surface.js +5 -4
  398. package/dist/peer-directory.d.ts +6 -8
  399. package/dist/peer-directory.js +3 -2
  400. package/dist/permission-rule-vocab.d.ts +53 -8
  401. package/dist/permission-rule-vocab.js +30 -2
  402. package/dist/plugins/adoption-log-sql.d.ts +0 -22
  403. package/dist/plugins/adoption-log-sql.js +5 -3
  404. package/dist/plugins/approval-ask-retired-delete-by-task-m50.d.ts +7 -0
  405. package/dist/plugins/approval-ask-retired-delete-by-task-m50.js +17 -0
  406. package/dist/plugins/approval-ask-store-file.d.ts +18 -7
  407. package/dist/plugins/approval-ask-store-file.js +37 -14
  408. package/dist/plugins/approval-ask-store-memory.d.ts +12 -4
  409. package/dist/plugins/approval-ask-store-memory.js +24 -10
  410. package/dist/plugins/approval-ask-store-sql.d.ts +39 -20
  411. package/dist/plugins/approval-ask-store-sql.js +41 -10
  412. package/dist/plugins/approval-exemption-skill-m46.d.ts +20 -0
  413. package/dist/plugins/approval-exemption-skill-m46.js +25 -0
  414. package/dist/plugins/approval-exemption-store.d.ts +6 -1
  415. package/dist/plugins/approval-exemption-store.js +14 -0
  416. package/dist/plugins/approval-nonce-store.d.ts +39 -12
  417. package/dist/plugins/approval-nonce-store.js +72 -23
  418. package/dist/plugins/blob-backend.d.ts +2 -0
  419. package/dist/plugins/blob-backend.js +9 -6
  420. package/dist/plugins/caching-session-store.d.ts +13 -1
  421. package/dist/plugins/caching-session-store.js +43 -4
  422. package/dist/plugins/checkpoint-index-columns.d.ts +32 -5
  423. package/dist/plugins/checkpoint-index-columns.js +36 -1
  424. package/dist/plugins/checkpoint-store-face.d.ts +83 -10
  425. package/dist/plugins/checkpoint-store-face.js +18 -1
  426. package/dist/plugins/device-store-sql.js +1 -0
  427. package/dist/plugins/ensure-index.d.ts +13 -1
  428. package/dist/plugins/ensure-index.js +154 -21
  429. package/dist/plugins/ensure-schema-lock-policy.d.ts +0 -15
  430. package/dist/plugins/ensure-schema-lock-policy.js +3 -2
  431. package/dist/plugins/file-run-store.d.ts +63 -23
  432. package/dist/plugins/file-run-store.js +138 -83
  433. package/dist/plugins/host-platform.d.ts +24 -3
  434. package/dist/plugins/host-platform.js +15 -1
  435. package/dist/plugins/image-bake-store-sql.js +2 -0
  436. package/dist/plugins/imported-layer-root-retirement.d.ts +2 -2
  437. package/dist/plugins/k8s-pod-reaper.d.ts +1 -3
  438. package/dist/plugins/k8s-pod-reaper.js +0 -1
  439. package/dist/plugins/leader-run-store-sql.d.ts +22 -0
  440. package/dist/plugins/leader-run-store-sql.js +34 -4
  441. package/dist/plugins/local-checkpoint-store.d.ts +58 -23
  442. package/dist/plugins/local-checkpoint-store.js +195 -76
  443. package/dist/plugins/local-data-dir-modes-m39.js +2 -1
  444. package/dist/plugins/local-docker-capacity.d.ts +93 -0
  445. package/dist/plugins/local-docker-capacity.js +194 -0
  446. package/dist/plugins/local-docker-reaper.d.ts +92 -0
  447. package/dist/plugins/local-docker-reaper.js +102 -0
  448. package/dist/plugins/local-engine-high-water.d.ts +4 -2
  449. package/dist/plugins/local-engine-high-water.js +4 -1
  450. package/dist/plugins/local-session-store.d.ts +358 -125
  451. package/dist/plugins/local-session-store.js +824 -251
  452. package/dist/plugins/local-session-window.d.ts +3 -2
  453. package/dist/plugins/local-session-window.js +3 -2
  454. package/dist/plugins/local-sidecar-quarantine.js +3 -2
  455. package/dist/plugins/local-task-attachment-store.d.ts +18 -1
  456. package/dist/plugins/local-task-attachment-store.js +22 -1
  457. package/dist/plugins/memory-embedder.js +9 -7
  458. package/dist/plugins/memory-engine-pg.d.ts +3 -2
  459. package/dist/plugins/memory-engine-pg.js +14 -5
  460. package/dist/plugins/memory-engine-sql-apply.d.ts +8 -4
  461. package/dist/plugins/memory-engine-sql-apply.js +6 -8
  462. package/dist/plugins/memory-engine-tidb.d.ts +4 -2
  463. package/dist/plugins/memory-engine-tidb.js +11 -2
  464. package/dist/plugins/memory-optout-grant-store-sql.d.ts +1 -1
  465. package/dist/plugins/memory-optout-grant-store-sql.js +3 -3
  466. package/dist/plugins/memory-run-store.d.ts +39 -27
  467. package/dist/plugins/memory-run-store.js +72 -70
  468. package/dist/plugins/one-time-migration-marker.d.ts +10 -0
  469. package/dist/plugins/one-time-migration-marker.js +7 -0
  470. package/dist/plugins/one-time-migrations.d.ts +186 -45
  471. package/dist/plugins/one-time-migrations.js +442 -119
  472. package/dist/plugins/org-delivery-quota-store.js +2 -1
  473. package/dist/plugins/permission-rule-store-file.d.ts +3 -3
  474. package/dist/plugins/permission-rule-store-sql.d.ts +4 -63
  475. package/dist/plugins/permission-rule-store-sql.js +6 -86
  476. package/dist/plugins/pg-pool.d.ts +1 -31
  477. package/dist/plugins/pg-pool.js +16 -7
  478. package/dist/plugins/remote-env-e2b.d.ts +7 -25
  479. package/dist/plugins/remote-env-e2b.js +34 -25
  480. package/dist/plugins/remote-env-host.d.ts +2 -9
  481. package/dist/plugins/remote-env-host.js +34 -22
  482. package/dist/plugins/remote-env-k8s.d.ts +40 -45
  483. package/dist/plugins/remote-env-k8s.js +107 -58
  484. package/dist/plugins/remote-env-local-docker.d.ts +148 -14
  485. package/dist/plugins/remote-env-local-docker.js +367 -72
  486. package/dist/plugins/remote-scratchpad.js +4 -0
  487. package/dist/plugins/retention-deployment.d.ts +49 -17
  488. package/dist/plugins/retention-deployment.js +39 -8
  489. package/dist/plugins/run-preview-m45.d.ts +11 -0
  490. package/dist/plugins/run-preview-m45.js +19 -0
  491. package/dist/plugins/run-store-sql.d.ts +56 -68
  492. package/dist/plugins/run-store-sql.js +127 -116
  493. package/dist/plugins/s3-answer.d.ts +2 -1
  494. package/dist/plugins/sandbox-reaper.d.ts +26 -0
  495. package/dist/plugins/sandbox-reaper.js +2 -0
  496. package/dist/plugins/scenario-prefix-switch-m42.d.ts +54 -0
  497. package/dist/plugins/scenario-prefix-switch-m42.js +55 -0
  498. package/dist/plugins/scheduler-support.d.ts +16 -1
  499. package/dist/plugins/scheduler-support.js +22 -2
  500. package/dist/plugins/schema-consistency.d.ts +42 -9
  501. package/dist/plugins/schema-consistency.js +212 -11
  502. package/dist/plugins/schema-drift-sentence.d.ts +4 -1
  503. package/dist/plugins/schema-drift-sentence.js +4 -4
  504. package/dist/plugins/schema-ensure-legs.d.ts +9 -0
  505. package/dist/plugins/schema-ensure-legs.js +1 -1
  506. package/dist/plugins/schema-indexes.js +3 -0
  507. package/dist/plugins/schema-live-columns.d.ts +14 -5
  508. package/dist/plugins/schema-live-columns.js +10 -10
  509. package/dist/plugins/send-user-file.js +2 -1
  510. package/dist/plugins/session-birth.d.ts +37 -0
  511. package/dist/plugins/session-birth.js +38 -0
  512. package/dist/plugins/session-epoch-anchor.d.ts +40 -12
  513. package/dist/plugins/session-epoch-anchor.js +33 -11
  514. package/dist/plugins/session-ext-table.d.ts +27 -3
  515. package/dist/plugins/session-ext-table.js +19 -6
  516. package/dist/plugins/session-fork.d.ts +15 -1
  517. package/dist/plugins/session-fork.js +3 -0
  518. package/dist/plugins/session-ownership.d.ts +1 -1
  519. package/dist/plugins/session-placement.d.ts +22 -16
  520. package/dist/plugins/session-placement.js +15 -12
  521. package/dist/plugins/session-row-sql.d.ts +12 -0
  522. package/dist/plugins/session-row-sql.js +3 -3
  523. package/dist/plugins/session-run-rollup.d.ts +29 -0
  524. package/dist/plugins/session-run-rollup.js +40 -0
  525. package/dist/plugins/session-store-sql.d.ts +38 -12
  526. package/dist/plugins/session-store-sql.js +130 -30
  527. package/dist/plugins/session-withdraw-sql.d.ts +36 -0
  528. package/dist/plugins/session-withdraw-sql.js +42 -0
  529. package/dist/plugins/shared-memory-store-sql.d.ts +0 -69
  530. package/dist/plugins/shared-memory-store-sql.js +2 -1
  531. package/dist/plugins/shell-sentinel-owner-m48.d.ts +20 -0
  532. package/dist/plugins/shell-sentinel-owner-m48.js +3 -0
  533. package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
  534. package/dist/plugins/sql-driver.d.ts +1 -1
  535. package/dist/plugins/sql-driver.js +2 -1
  536. package/dist/plugins/sql-errors.d.ts +32 -5
  537. package/dist/plugins/sql-errors.js +55 -7
  538. package/dist/plugins/sql-json-column.d.ts +8 -7
  539. package/dist/plugins/sql-json-column.js +10 -6
  540. package/dist/plugins/store-backend.d.ts +84 -15
  541. package/dist/plugins/store-backend.js +186 -55
  542. package/dist/plugins/store-contracts.d.ts +90 -30
  543. package/dist/plugins/store-contracts.js +22 -1
  544. package/dist/plugins/store-fault.d.ts +28 -2
  545. package/dist/plugins/store-fault.js +20 -2
  546. package/dist/plugins/task-attachment-store.d.ts +15 -3
  547. package/dist/plugins/task-attachment-store.js +22 -9
  548. package/dist/plugins/tidb-pool.js +23 -8
  549. package/dist/plugins/web-search.js +5 -3
  550. package/dist/plugins/workflow-run-store-sql.d.ts +6 -1
  551. package/dist/plugins/workflow-run-store-sql.js +11 -5
  552. package/dist/principal-jwt.d.ts +4 -4
  553. package/dist/principal-jwt.js +20 -5
  554. package/dist/principal-shape.d.ts +41 -0
  555. package/dist/principal-shape.js +19 -0
  556. package/dist/question.d.ts +13 -23
  557. package/dist/question.js +21 -39
  558. package/dist/request-declaration-refusal.d.ts +35 -12
  559. package/dist/request-declaration-refusal.js +21 -9
  560. package/dist/request-form.d.ts +58 -0
  561. package/dist/request-form.js +72 -0
  562. package/dist/request-image-urls.d.ts +8 -2
  563. package/dist/request-image-urls.js +2 -1
  564. package/dist/request-images.d.ts +20 -0
  565. package/dist/request-images.js +59 -0
  566. package/dist/request-key-closure.d.ts +6 -7
  567. package/dist/request-key-closure.js +15 -2
  568. package/dist/resource-suspend.d.ts +7 -16
  569. package/dist/resource-suspend.js +1 -1
  570. package/dist/retired-name-host-notice.d.ts +21 -0
  571. package/dist/retired-name-host-notice.js +12 -0
  572. package/dist/rule-entry-list.d.ts +2 -2
  573. package/dist/rule-entry-list.js +3 -4
  574. package/dist/rule-issue-reading.d.ts +58 -0
  575. package/dist/rule-issue-reading.js +14 -0
  576. package/dist/rules-consent.d.ts +5 -3
  577. package/dist/rules-consent.js +1 -0
  578. package/dist/run-acceptance.d.ts +3 -1
  579. package/dist/run-acceptance.js +3 -0
  580. package/dist/run-local.d.ts +14 -6
  581. package/dist/run-local.js +92 -80
  582. package/dist/run-stale-default.d.ts +7 -0
  583. package/dist/run-stale-default.js +2 -0
  584. package/dist/runs.d.ts +26 -15
  585. package/dist/runs.js +39 -27
  586. package/dist/runtime-caps-resolver.d.ts +21 -6
  587. package/dist/runtime-caps-resolver.js +17 -4
  588. package/dist/runtime-governance.d.ts +2 -2
  589. package/dist/runtime-governance.js +5 -3
  590. package/dist/sealed-key-layout.d.ts +12 -5
  591. package/dist/sealed-key-layout.js +5 -0
  592. package/dist/sealed-key.d.ts +12 -2
  593. package/dist/sealed-key.js +115 -27
  594. package/dist/security.d.ts +128 -158
  595. package/dist/security.js +163 -59
  596. package/dist/sema-config-dir.d.ts +20 -0
  597. package/dist/sema-config-dir.js +25 -0
  598. package/dist/server-secret-env.d.ts +21 -5
  599. package/dist/server-secret-env.js +14 -4
  600. package/dist/session-children-retire.d.ts +7 -5
  601. package/dist/session-children-retire.js +7 -2
  602. package/dist/session-deletion.d.ts +88 -0
  603. package/dist/session-deletion.js +8 -0
  604. package/dist/session-erase.d.ts +145 -0
  605. package/dist/session-erase.js +115 -0
  606. package/dist/session-error-wire.d.ts +5 -5
  607. package/dist/session-error-wire.js +1 -1
  608. package/dist/session-incarnation.d.ts +52 -5
  609. package/dist/session-incarnation.js +19 -2
  610. package/dist/session-policy-wire.d.ts +6 -3
  611. package/dist/session-policy-wire.js +11 -1
  612. package/dist/session-resident-background.d.ts +0 -71
  613. package/dist/session-resident-background.js +4 -3
  614. package/dist/session-row.d.ts +2 -1
  615. package/dist/session-rules-seat.d.ts +12 -0
  616. package/dist/session-rules-seat.js +19 -0
  617. package/dist/session-titler.d.ts +7 -15
  618. package/dist/session-titler.js +1 -1
  619. package/dist/spec-fields.d.ts +57 -3
  620. package/dist/spec-fields.js +72 -51
  621. package/dist/store-posture.d.ts +136 -20
  622. package/dist/store-posture.js +188 -3
  623. package/dist/stored-request-hooks-m20.js +2 -2
  624. package/dist/stores-sql/background-agent-store-sql.d.ts +11 -2
  625. package/dist/stores-sql/background-agent-store-sql.js +73 -20
  626. package/dist/stores-sql/checkpoint-store-sql.d.ts +33 -28
  627. package/dist/stores-sql/checkpoint-store-sql.js +75 -44
  628. package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
  629. package/dist/stores-sql/mailbox-store-sql.js +31 -32
  630. package/dist/stores-sql/pg-session-storage.d.ts +7 -5
  631. package/dist/stores-sql/pg-session-storage.js +54 -46
  632. package/dist/stores-sql/retention-store-sql.d.ts +87 -19
  633. package/dist/stores-sql/retention-store-sql.js +92 -27
  634. package/dist/stores-sql/session-policy-store-sql.d.ts +32 -10
  635. package/dist/stores-sql/session-policy-store-sql.js +44 -36
  636. package/dist/stores-sql/session-write-hooks.d.ts +12 -6
  637. package/dist/stores-sql/tidb-session-storage.d.ts +4 -4
  638. package/dist/stores-sql/tidb-session-storage.js +13 -8
  639. package/dist/stores-sql/tidb-session-store.d.ts +36 -17
  640. package/dist/stores-sql/tidb-session-store.js +69 -39
  641. package/dist/stores-sql/windowed-session-storage.d.ts +5 -0
  642. package/dist/stores-sql/windowed-session-storage.js +9 -0
  643. package/dist/submission-exit.d.ts +50 -0
  644. package/dist/submission-exit.js +108 -0
  645. package/dist/task-a2a.d.ts +1 -1
  646. package/dist/task-a2a.js +3 -3
  647. package/dist/task-cwd.d.ts +20 -0
  648. package/dist/task-cwd.js +8 -0
  649. package/dist/task-mcp.d.ts +1 -1
  650. package/dist/task-mcp.js +3 -3
  651. package/dist/task-settings.d.ts +14 -10
  652. package/dist/task-settings.js +18 -7
  653. package/dist/terminal.d.ts +25 -43
  654. package/dist/terminal.js +31 -11
  655. package/dist/tool-approval.d.ts +131 -129
  656. package/dist/tool-approval.js +189 -193
  657. package/dist/trace/core-keyset-guard.d.ts +13 -6
  658. package/dist/trace/core-vocab-wire.d.ts +95 -0
  659. package/dist/trace/core-vocab-wire.js +15 -0
  660. package/dist/trace/exit-seat.d.ts +30 -4
  661. package/dist/trace/exit-seat.js +40 -36
  662. package/dist/trace/extension-call-face.d.ts +3 -2
  663. package/dist/trace/extension-call-face.js +3 -1
  664. package/dist/trace/ledger-events.d.ts +4 -7
  665. package/dist/trace/ledger-sink.d.ts +1 -30
  666. package/dist/trace/ledger-sink.js +0 -11
  667. package/dist/trace/model-segment.d.ts +5 -5
  668. package/dist/trace/model-segment.js +5 -5
  669. package/dist/trace/project.d.ts +55 -42
  670. package/dist/trace/project.js +52 -19
  671. package/dist/trace/redact.d.ts +123 -27
  672. package/dist/trace/redact.js +880 -260
  673. package/dist/trace/self-minted-wire.d.ts +487 -19
  674. package/dist/trace/self-minted-wire.js +172 -23
  675. package/dist/trace/wire-projection-faces.d.ts +5 -6
  676. package/dist/trace/wire-projection-faces.js +2 -2
  677. package/dist/upstream-body.d.ts +23 -0
  678. package/dist/upstream-body.js +16 -0
  679. package/dist/url-refusal.d.ts +3 -3
  680. package/dist/url-refusal.js +11 -4
  681. package/dist/user-authored-text.d.ts +1 -1
  682. package/dist/user-input-ledger.d.ts +7 -8
  683. package/dist/user-input-ledger.js +1 -1
  684. package/dist/web-admin-client/apply-effective.d.ts +6 -3
  685. package/dist/web-admin-client/apply-effective.js +13 -16
  686. package/dist/web-admin-client/apply-ledger.d.ts +16 -7
  687. package/dist/web-admin-client/apply-ledger.js +5 -2
  688. package/dist/web-admin-client/center-request.d.ts +2 -2
  689. package/dist/web-admin-client/center-request.js +15 -15
  690. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  691. package/dist/web-admin-client/effective-keys.js +1 -1
  692. package/dist/web-admin-client/entry-metadata.d.ts +8 -0
  693. package/dist/web-admin-client/entry-metadata.js +13 -0
  694. package/dist/web-admin-client/http-client.d.ts +1 -1
  695. package/dist/web-admin-client/http-client.js +23 -4
  696. package/dist/web-admin-client/mcp-revocation.d.ts +1 -1
  697. package/dist/web-admin-client/mcp-revocation.js +2 -1
  698. package/dist/web-admin-client/read-warnings.d.ts +1 -0
  699. package/dist/web-admin-client/read-warnings.js +1 -0
  700. package/dist/web-admin-client/restart-signal.js +8 -4
  701. package/dist/web-admin-client/skills-mcp.d.ts +2 -2
  702. package/dist/web-admin-client/types.d.ts +1 -2
  703. package/dist/workspace-space.d.ts +3 -2
  704. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
  705. package/package.json +6 -3
  706. package/scripts/drain-check-local.mjs +118 -147
  707. package/scripts/drain-check.mysql.sql +22 -2
  708. package/scripts/drain-check.pg.sql +22 -2
  709. package/wire-changes/server@7.148.0.json +1517 -0
  710. package/dist/http/memory-control-plane-corrupt-reply.d.ts +0 -25
  711. package/dist/http/memory-control-plane-corrupt-reply.js +0 -19
  712. package/dist/http/memory-layer-reply.d.ts +0 -30
  713. package/dist/http/memory-layer-reply.js +0 -24
  714. package/dist/lsp/e2b-bridge.d.ts +0 -57
  715. package/dist/lsp/e2b-bridge.js +0 -139
  716. package/dist/lsp/e2b-manager.d.ts +0 -93
  717. package/dist/lsp/e2b-manager.js +0 -178
  718. package/dist/lsp/lsp-frames.d.ts +0 -14
  719. package/dist/lsp/lsp-frames.js +0 -25
  720. package/dist/lsp/manager.d.ts +0 -129
  721. package/dist/lsp/manager.js +0 -127
  722. package/dist/lsp/types.d.ts +0 -12
  723. package/dist/lsp/types.js +0 -2
  724. package/dist/lsp/ws-transport.d.ts +0 -40
  725. package/dist/lsp/ws-transport.js +0 -210
@@ -1,22 +1,24 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-env.js";
3
3
  import vm from "node:vm";
4
- import { hostShell, resolveHostShell, adoptHostForegroundChild, killTreeSoft, killTreeHard } from "../plugins/host-platform.js";
4
+ import { hostShell, resolveHostShell, adoptHostForegroundChild, killTreeSoft, killTreeHard, signalProcessTree, reapedLeaderGroupAlive } from "../plugins/host-platform.js";
5
5
  import { classifyHooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_ENTRY_TYPES, HOOK_EVENTS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, hookMatcherForm, hookMatcherSelects, interpolateHookEnvVars, } from "@sema-agent/settings-schema/hooks";
6
- import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
6
+ import { findUrlCredential, outgoingPath } from "@sema-agent/settings-schema";
7
7
  import { httpHookEnvVarsAllowed, httpHookUrlVerdict } from "@sema-agent/settings-schema";
8
8
  import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
9
9
  import { describeToolCatalog } from "@sema-agent/core";
10
- import { hangClassic, wrapClassic } from "./classic-stations.js";
10
+ import { hangClassic } from "./classic-stations.js";
11
11
  import { MAX_HOOK_NOTICE_TEXT_CHARS, boundedNoticeText } from "../fleet/fleet-bus.js";
12
12
  import { recordFailOpen } from "../observability/fail-open.js";
13
- import { LIMIT_REASONS } from "../limit-refusal.js";
13
+ import { LIMIT_REASONS, limitFacts } from "../limit-refusal.js";
14
+ import { fieldCoordinate } from "../http/refusal-body.js";
14
15
  import { clipDataText, echoCallerText } from "../caller-text-echo.js";
15
16
  import { seatText } from "../trace/exit-seat.js";
16
17
  import { redactSecrets, buildDisplaySafeUrl } from "../trace/redact.js";
17
18
  import { canonicalJsonStringify } from "@sema-agent/core";
18
19
  import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
19
20
  import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
21
+ import { createEgressFetch } from "../egress-redirect.js";
20
22
  import { renderBranchTranscript } from "./branch-transcript.js";
21
23
  import { buildRequestUrlCredentialRefusal, urlCredentialHeaderExit } from "../url-credential-refusal.js";
22
24
  import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookLaneOf, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
@@ -42,31 +44,26 @@ function hookEnv(ctx) {
42
44
  return hookChildEnv(composeHookEnvLayer(ctx.shellEnv, ctx.cwd, undefined), false);
43
45
  }
44
46
  export const SETTINGS_HOOK_STATIONS = {
45
- PreToolUse: ["classic.PreToolUse"],
46
- PostToolUse: ["classic.PostToolUse"],
47
- PostToolUseFailure: ["classic.PostToolUseFailure"],
48
- PostToolBatch: ["classic.PostToolBatch"],
49
- UserPromptSubmit: ["classic.UserPromptSubmit"],
50
- Stop: ["classic.Stop", "classic.SubagentStop"],
51
- StopFailure: ["classic.StopFailure"],
52
- PreCompact: ["classic.PreCompact"],
53
- PostCompact: ["classic.PostCompact"],
47
+ PreToolUse: "classic.PreToolUse",
48
+ PostToolUse: "classic.PostToolUse",
49
+ PostToolUseFailure: "classic.PostToolUseFailure",
50
+ PostToolBatch: "classic.PostToolBatch",
51
+ UserPromptSubmit: "classic.UserPromptSubmit",
52
+ Stop: "classic.Stop",
53
+ StopFailure: "classic.StopFailure",
54
+ PreCompact: "classic.PreCompact",
55
+ PostCompact: "classic.PostCompact",
54
56
  };
55
57
  function chainsBelowTaskOn(patterns, station) {
56
58
  return patterns.some((p) => p === station || (p.endsWith(".*") && station.startsWith(p.slice(0, -1))));
57
59
  }
58
- function managedTightenOnlyOn(lane, station, belowTaskChainPatterns) {
59
- return lane === "managed" && chainsBelowTaskOn(belowTaskChainPatterns, station);
60
- }
61
- const MANAGED_REWRITE_UNSHOWN_REASON = "a managed PreToolUse hook rewrote this call's input, but this deployment runs its own handler for this call after the request's hooks and that handler never sees a rewrite made above it, so the rewrite cannot be honored and the call was not executed";
62
- const MANAGED_ALLOW_UNJUDGED_REASON = "a managed PreToolUse hook allowed this call, but this deployment runs its own handler for this call after the request's hooks, and a request hook's allow does not stand in for that handler's judgment, so the call was not executed (the same outcome as the same hook without a managed source)";
63
- function hangTranslator(on, event, translators, lane) {
60
+ const REQUEST_HOOK_REWRITE_UNSHOWN_REASON = "a request PreToolUse hook rewrote this call's input, but this deployment runs its own handler for this call after the request's hooks and that handler never sees a rewrite made above it, so the rewrite cannot be honored and the call was not executed";
61
+ const REQUEST_HOOK_ALLOW_UNJUDGED_REASON = "a request PreToolUse hook allowed this call, but this deployment runs its own handler for this call after the request's hooks, and a request hook's allow does not stand in for that handler's judgment, so the call was not executed";
62
+ function hangTranslator(on, event, translators) {
64
63
  const translate = translators[event];
65
64
  if (translate === undefined)
66
65
  return;
67
- const hang = lane === "managed" ? wrapClassic : hangClassic;
68
- for (const station of SETTINGS_HOOK_STATIONS[event])
69
- hang(on, station, translate);
66
+ hangClassic(on, SETTINGS_HOOK_STATIONS[event], translate);
70
67
  }
71
68
  export const SETTINGS_HOOKS_REGISTRATIONS = {
72
69
  managed: "settings.hooks.managed",
@@ -242,54 +239,61 @@ function hooksCapIssue(limit, event, path, count) {
242
239
  }
243
240
  const matcherPath = (event, groupIndex) => ["hooks", event, groupIndex, "matcher"];
244
241
  export function buildHooksRefusal(issue) {
245
- const field = "settings.hooks";
246
242
  switch (issue.reason) {
247
- case "dead_alias":
243
+ case "dead_alias": {
244
+ const field = hooksFieldCoordinate(matcherPath(issue.event, issue.groupIndex));
248
245
  return {
249
246
  message: `settings.hooks.${issue.event}: matcher "${issue.alias}" is an alias; tool-event matchers match the tool's wire name (PostToolUse / PostToolUseFailure since 7.107.0) — use "${issue.canonical}"`,
250
- field,
251
- detail: { reason: issue.reason, event: issue.event, alias: issue.alias, canonical: issue.canonical, path: matcherPath(issue.event, issue.groupIndex) },
247
+ facts: { field, reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, alias: issue.alias, canonical: issue.canonical },
252
248
  };
253
- case "over_budget":
249
+ }
250
+ case "over_budget": {
251
+ const field = hooksFieldCoordinate(matcherPath(issue.event, issue.groupIndex));
254
252
  return {
255
- message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: evaluating the pattern did not finish within ${issue.budgetMs} ms (catastrophic regex backtracking) — rewrite it as a tool-name list or a pattern without nested repetition`,
256
- field,
257
- detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, budgetMs: issue.budgetMs, path: matcherPath(issue.event, issue.groupIndex) },
253
+ message: `${field}: evaluating the pattern did not finish within ${issue.budgetMs} ms (catastrophic regex backtracking) — rewrite it as a tool-name list or a pattern without nested repetition`,
254
+ facts: { field, reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, budgetMs: issue.budgetMs },
258
255
  };
259
- case "budget_exceeded":
256
+ }
257
+ case "budget_exceeded": {
258
+ const field = hooksFieldCoordinate(matcherPath(issue.event, issue.groupIndex));
260
259
  return {
261
- message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: evaluating this configuration's regular-expression matchers took longer than ${issue.totalMs} ms in total (very slow patterns) — write tool names as a list such as "Read|Edit", or simplify the patterns`,
262
- field,
263
- detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, totalMs: issue.totalMs, path: matcherPath(issue.event, issue.groupIndex) },
260
+ message: `${field}: evaluating this configuration's regular-expression matchers took longer than ${issue.totalMs} ms in total (very slow patterns) — write tool names as a list such as "Read|Edit", or simplify the patterns`,
261
+ facts: { field, reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, totalMs: issue.totalMs },
264
262
  };
265
- case "invalid_regex":
263
+ }
264
+ case "invalid_regex": {
265
+ const field = hooksFieldCoordinate(matcherPath(issue.event, issue.groupIndex));
266
266
  return {
267
- message: `settings.hooks.${issue.event}[${issue.groupIndex}].matcher: ${JSON.stringify(issue.matcher)} is not a valid regular expression (${issue.compileError}) — fix the pattern, or write the tool names as a list such as "Read|Edit"`,
268
- field,
269
- detail: { reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex, path: matcherPath(issue.event, issue.groupIndex) },
267
+ message: `${field}: ${JSON.stringify(issue.matcher)} is not a valid regular expression (${issue.compileError}) — fix the pattern, or write the tool names as a list such as "Read|Edit"`,
268
+ facts: { field, reason: issue.reason, event: issue.event, groupIndex: issue.groupIndex },
270
269
  };
271
- case "unsupported_entry_type":
270
+ }
271
+ case "unsupported_entry_type": {
272
+ const field = hooksFieldCoordinate(issue.path);
272
273
  return {
273
- message: `${hooksCoordinate(issue.path)}: this server does not run hook entries of type ${issue.type}; supported: ${SUPPORTED_HOOK_ENTRY_TYPES.join(" / ")}`,
274
- field,
275
- detail: { reason: issue.reason, path: hooksOutgoingPath(issue.path), type: issue.type },
274
+ message: `${field}: this server does not run hook entries of type ${issue.type}; supported: ${SUPPORTED_HOOK_ENTRY_TYPES.join(" / ")}`,
275
+ facts: { field, reason: issue.reason, type: issue.type },
276
276
  };
277
+ }
277
278
  case "over_cap":
278
279
  case "too_long": {
279
280
  const cap = issue;
280
- return { message: HOOKS_CAPS[cap.limit].sentence(cap.event, cap.count), field, detail: { reason: cap.reason, path: hooksOutgoingPath(cap.path), count: cap.count, cap: cap.cap } };
281
+ return { message: HOOKS_CAPS[cap.limit].sentence(cap.event, cap.count), facts: { ...limitFacts(hooksFieldCoordinate(cap.path), HOOKS_CAPS[cap.limit].measure, cap.count, cap.cap), reason: cap.reason } };
282
+ }
283
+ case "unnamed_refusal": {
284
+ const field = hooksFieldCoordinate(issue.path);
285
+ return { message: `${field}: the hooks reader refused this configuration without naming a piece`, facts: { field, reason: issue.reason } };
286
+ }
287
+ case "wire_key_invalid": {
288
+ const field = hooksFieldCoordinate(issue.path);
289
+ return { message: `${field}: ${HOOKS_WIRE_KEY_SHAPE[issue.key]}`, facts: { field, reason: issue.reason, key: issue.key } };
281
290
  }
282
- case "unnamed_refusal":
283
- return { message: `${hooksCoordinate(issue.path)}: the hooks reader refused this configuration without naming a piece`, field, detail: { reason: issue.reason, path: hooksOutgoingPath(issue.path) } };
284
- case "wire_key_invalid":
285
- return { message: `${hooksCoordinate(issue.path)}: ${HOOKS_WIRE_KEY_SHAPE[issue.key]}`, field, detail: { reason: issue.reason, path: hooksOutgoingPath(issue.path), key: issue.key } };
286
291
  default: {
287
292
  const piece = issue;
288
293
  const key = piece.key !== undefined ? hooksOutgoingKey(piece.key) : undefined;
289
294
  return {
290
295
  message: hooksPieceRefusal(piece),
291
- field,
292
- detail: { reason: piece.reason, path: hooksOutgoingPath(piece.path), ...(key !== undefined ? { key } : {}) },
296
+ facts: { field: hooksFieldCoordinate(piece.path), reason: piece.reason, ...(key !== undefined ? { key } : {}) },
293
297
  };
294
298
  }
295
299
  }
@@ -352,8 +356,8 @@ export function hooksUrlCredentialIssue(config) {
352
356
  return undefined;
353
357
  }
354
358
  export function buildHooksUrlCredentialRefusal(issue) {
355
- const entry = `settings.hooks.${issue.event}[${issue.groupIndex}].hooks[${issue.entryIndex}]`;
356
- return buildRequestUrlCredentialRefusal(`${entry}.url`, issue.credential, urlCredentialHeaderExit(`${entry}.headers`));
359
+ const entry = ["hooks", issue.event, issue.groupIndex, "hooks", issue.entryIndex];
360
+ return buildRequestUrlCredentialRefusal(hooksFieldCoordinate([...entry, "url"]), issue.credential, urlCredentialHeaderExit(hooksFieldCoordinate([...entry, "headers"])));
357
361
  }
358
362
  const HOOKS_PIECE_MEANING = {
359
363
  "hooks-not-object": "`hooks` must be an object keyed by hook event name",
@@ -371,8 +375,8 @@ const HOOKS_PIECE_MEANING = {
371
375
  export function hooksOutgoingPath(path) {
372
376
  return outgoingPath(path).map((seg) => (typeof seg === "string" ? echoCallerText(seg) : seg));
373
377
  }
374
- export function hooksCoordinate(path) {
375
- return `settings.${formatConfigPath(hooksOutgoingPath(path))}`;
378
+ export function hooksFieldCoordinate(path) {
379
+ return fieldCoordinate("settings", hooksOutgoingPath(path));
376
380
  }
377
381
  function hooksOutgoingKey(key) {
378
382
  return echoCallerText(String(outgoingPath([key])[0] ?? ""));
@@ -382,7 +386,7 @@ function hooksPieceIssue(piece) {
382
386
  }
383
387
  function hooksPieceRefusal(piece) {
384
388
  const field = piece.key !== undefined ? ` (field: ${hooksOutgoingKey(piece.key)})` : "";
385
- return `${hooksCoordinate(piece.path)}: ${piece.reason} — ${HOOKS_PIECE_MEANING[piece.reason]}${field}`;
389
+ return `${hooksFieldCoordinate(piece.path)}: ${piece.reason} — ${HOOKS_PIECE_MEANING[piece.reason]}${field}`;
386
390
  }
387
391
  function hooksRefused(issue, pieces) {
388
392
  const error = buildHooksRefusal(issue).message;
@@ -733,24 +737,26 @@ const VERDICT_GAP_CAUSE = {
733
737
  input_truncated: "verdict_unreadable",
734
738
  matcher_over_budget: "verdict_unreadable",
735
739
  };
736
- function createUnrunLedger(ctx, event, toolName) {
740
+ function createUnrunLedger(ctx, event, toolName, once) {
737
741
  let managedVerdictGap;
738
742
  let haltNoted = false;
739
743
  const ledger = {
740
744
  halted(source, entry) {
741
- if (haltNoted)
745
+ if (haltNoted || !isSyncEntry(entry))
742
746
  return;
743
747
  haltNoted = true;
744
748
  ledger.unrun(source, entry, "budget_exhausted", ENGINE_BUDGET_SPENT_DETAIL);
745
749
  },
746
750
  untrusted(source, entry, reason) {
751
+ once.unsettled(entry);
747
752
  if (hookLaneOf(source) === "unmanaged")
748
753
  return;
749
754
  managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
750
755
  noteVerdictUntrusted(ctx, { event, reason, entry, source, toolName });
751
756
  },
752
757
  unrun(source, entry, reason, detail) {
753
- if (source === undefined || hookLaneOf(source) === "unmanaged")
758
+ once.unsettled(entry);
759
+ if (source === undefined || hookLaneOf(source) === "unmanaged" || !isSyncEntry(entry))
754
760
  return;
755
761
  managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
756
762
  ctx.logger.warn("hook_managed_unrun", { event, reason, source: echoCallerText(source), type: entry.type, ...(toolName !== undefined ? { tool: toolName } : {}) });
@@ -830,8 +836,12 @@ function withBelowContext(parts, below) {
830
836
  const tail = belowString(below, "additionalContext");
831
837
  return tail === undefined ? [...parts] : [...parts, tail];
832
838
  }
833
- function alsoAsks(below, own) {
834
- return below === undefined || below === own ? own : `${below} (a managed PreToolUse hook also asks: ${own})`;
839
+ const ALSO_ASKS_SPEAKER = {
840
+ managed: "a managed PreToolUse hook",
841
+ unmanaged: "a PreToolUse hook",
842
+ };
843
+ function alsoAsks(lane, below, own) {
844
+ return below === undefined || below === own ? own : `${below} (${ALSO_ASKS_SPEAKER[lane]} also asks: ${own})`;
835
845
  }
836
846
  function readBelowPreToolUse(below) {
837
847
  const action = belowString(below, "action");
@@ -845,7 +855,7 @@ function readBelowPreToolUse(below) {
845
855
  allowed: action === "allow",
846
856
  ...(action === "ask" && (cause === undefined || cause === "asked") ? { asked: message } : {}),
847
857
  ...(action === "ask" && cause !== undefined && cause !== "asked" ? { unreadable: { message, cause: unreadableCause } } : {}),
848
- realApproval: action === "ask" && typeof below === "object" && below !== null && Reflect.get(below, "requiresRealApproval") === true,
858
+ ...(action === "ask" && typeof below === "object" && below !== null && Object.hasOwn(below, "requiresRealApproval") && Reflect.get(below, "requiresRealApproval") === true ? { realApproval: true } : {}),
849
859
  ...(rewrite !== null && typeof rewrite === "object" ? { updatedInput: rewrite } : {}),
850
860
  ...(additionalContext !== undefined ? { additionalContext } : {}),
851
861
  };
@@ -857,7 +867,65 @@ function spawnPlannedCommand(plan, ctx, stdio) {
857
867
  const sh = hostShell();
858
868
  return spawn(sh.shell, [...sh.args, plan.command], { cwd: ctx.cwd, env, stdio, detached: true });
859
869
  }
860
- async function runCommandHook(entry, payload, ctx, halt) {
870
+ function createHookTreeLadder(child, ctx, event) {
871
+ const leaderOurs = () => child.pid !== undefined && child.exitCode === null && child.signalCode === null;
872
+ const alive = () => leaderOurs() || (child.pid !== undefined && reapedLeaderGroupAlive(child.pid));
873
+ const shoot = (sig) => {
874
+ const pid = child.pid;
875
+ if (pid === undefined) {
876
+ child.kill(sig);
877
+ return;
878
+ }
879
+ if (leaderOurs()) {
880
+ signalProcessTree(pid, sig);
881
+ return;
882
+ }
883
+ if (!reapedLeaderGroupAlive(pid))
884
+ return;
885
+ try {
886
+ (sig === "SIGTERM" ? killTreeSoft : killTreeHard)(pid);
887
+ }
888
+ catch {
889
+ child.kill(sig);
890
+ }
891
+ };
892
+ let hardFired = false;
893
+ const hard = (end) => {
894
+ if (hardFired)
895
+ return;
896
+ hardFired = true;
897
+ shoot("SIGKILL");
898
+ const recheck = setTimeout(() => {
899
+ const clean = !alive();
900
+ if (!clean)
901
+ shoot("SIGKILL");
902
+ const fields = { event, type: "command", end, shots: clean ? 1 : 2, clean };
903
+ if (clean)
904
+ ctx.logger.info("hook_command_tree_swept", fields);
905
+ else
906
+ ctx.logger.warn("hook_command_tree_swept", fields);
907
+ }, HOOK_KILL_GRACE_MS);
908
+ recheck.unref();
909
+ };
910
+ return {
911
+ halt(onEscalate) {
912
+ shoot("SIGTERM");
913
+ const escalate = setTimeout(() => {
914
+ if (hardFired || !alive())
915
+ return;
916
+ onEscalate();
917
+ hard("halt");
918
+ }, HOOK_KILL_GRACE_MS);
919
+ escalate.unref();
920
+ },
921
+ kill: hard,
922
+ ended() {
923
+ if (alive())
924
+ hard("exit");
925
+ },
926
+ };
927
+ }
928
+ async function runCommandHook(entry, payload, ctx, halt, event) {
861
929
  const plan = await planCommandSpawn(entry, ctx.cwd, ctx.shellEnv);
862
930
  if (plan.kind === "refused")
863
931
  return { code: null, stdout: "", stderr: "", timedOut: false, spawnError: plan.error };
@@ -882,6 +950,7 @@ async function runCommandHook(entry, payload, ctx, halt) {
882
950
  return;
883
951
  }
884
952
  const ticket = adoptHostForegroundChild(child);
953
+ const tree = createHookTreeLadder(child, ctx, event);
885
954
  let stdout = "";
886
955
  let stderr = "";
887
956
  let timedOut = false;
@@ -900,40 +969,16 @@ async function runCommandHook(entry, payload, ctx, halt) {
900
969
  timedOut = true;
901
970
  if (sent !== undefined)
902
971
  sent = "SIGKILL";
903
- try {
904
- if (child.pid)
905
- process.kill(-child.pid, "SIGKILL");
906
- else
907
- child.kill("SIGKILL");
908
- }
909
- catch {
910
- child.kill("SIGKILL");
911
- }
912
972
  settle(null);
973
+ tree.kill("timeout");
913
974
  }, timeoutMs);
914
- const signalGroup = (sig) => {
915
- try {
916
- if (child.pid)
917
- (sig === "SIGTERM" ? killTreeSoft : killTreeHard)(child.pid);
918
- else
919
- child.kill(sig);
920
- }
921
- catch {
922
- child.kill(sig);
923
- }
924
- };
925
975
  const onHalt = () => {
926
976
  if (settled)
927
977
  return;
928
978
  sent = "SIGTERM";
929
- signalGroup("SIGTERM");
930
- const escalate = setTimeout(() => {
931
- if (child.exitCode !== null || child.signalCode !== null)
932
- return;
979
+ tree.halt(() => {
933
980
  sent = "SIGKILL";
934
- signalGroup("SIGKILL");
935
- }, HOOK_KILL_GRACE_MS);
936
- escalate.unref();
981
+ });
937
982
  };
938
983
  halt.addEventListener("abort", onHalt, { once: true });
939
984
  child.stdout?.on("data", (d) => {
@@ -952,7 +997,12 @@ async function runCommandHook(entry, payload, ctx, halt) {
952
997
  resolve({ code: null, stdout, stderr, timedOut, spawnError: String(e) });
953
998
  }
954
999
  });
955
- child.on("close", (code) => settle(code));
1000
+ child.on("close", (code) => {
1001
+ const natural = !settled && sent === undefined;
1002
+ settle(code);
1003
+ if (natural)
1004
+ tree.ended();
1005
+ });
956
1006
  child.stdin?.on("error", () => { });
957
1007
  child.stdin?.end(`${JSON.stringify(boundedToolInputPayload(payload))}\n`);
958
1008
  });
@@ -970,18 +1020,11 @@ async function fireAsyncCommandHook(entry, payload, ctx, event, halt) {
970
1020
  return;
971
1021
  const child = spawnPlannedCommand(plan, ctx, ["pipe", "ignore", rewake ? "pipe" : "ignore"]);
972
1022
  const ticket = adoptHostForegroundChild(child);
1023
+ const tree = createHookTreeLadder(child, ctx, event);
973
1024
  let killedByTimeout = false;
974
1025
  const timer = setTimeout(() => {
975
1026
  killedByTimeout = true;
976
- try {
977
- if (child.pid)
978
- process.kill(-child.pid, "SIGKILL");
979
- else
980
- child.kill("SIGKILL");
981
- }
982
- catch {
983
- child.kill("SIGKILL");
984
- }
1027
+ tree.kill("timeout");
985
1028
  }, timeoutMs);
986
1029
  timer.unref?.();
987
1030
  let stderr = "";
@@ -1000,6 +1043,8 @@ async function fireAsyncCommandHook(entry, payload, ctx, event, halt) {
1000
1043
  child.on("close", (code) => {
1001
1044
  clearTimeout(timer);
1002
1045
  ticket?.release();
1046
+ if (!killedByTimeout)
1047
+ tree.ended();
1003
1048
  noteAsyncOutcome(entry, ctx, event, payload, { code, rewake, killedByTimeout });
1004
1049
  if (!rewake || code !== 2)
1005
1050
  return;
@@ -1119,6 +1164,7 @@ function httpHookWithheldSentence(event, block) {
1119
1164
  return `settings.hooks.${event}: http hook not sent — ${why} (rule: ${block.rule}); ${exit}`;
1120
1165
  }
1121
1166
  const MAX_BLANKED_REFERENCES_LOGGED = 16;
1167
+ const httpHookFetch = createEgressFetch("declare the final address as this hook entry's url");
1122
1168
  async function runHttpHook(entry, payload, ctx, event) {
1123
1169
  const block = httpHookUrlVerdict(entry.url, ctx.httpHookAllowlistLayers, SERVER_WORKSPACE_TRUST);
1124
1170
  if (block !== undefined)
@@ -1146,12 +1192,11 @@ async function runHttpHook(entry, payload, ctx, event) {
1146
1192
  const ac = new AbortController();
1147
1193
  const timer = setTimeout(() => ac.abort(), timeoutMs);
1148
1194
  try {
1149
- const res = await fetch(entry.url, {
1195
+ const res = await httpHookFetch(entry.url, {
1150
1196
  method: "POST",
1151
1197
  headers,
1152
1198
  body: `${JSON.stringify(boundedToolInputPayload(payload))}\n`,
1153
1199
  signal: ac.signal,
1154
- redirect: "error",
1155
1200
  });
1156
1201
  let body = "";
1157
1202
  const reader = res.body?.getReader();
@@ -1277,7 +1322,7 @@ function runRunnableEntry(entry, payload, ctx, event, llmExtra, halt) {
1277
1322
  case "agent":
1278
1323
  return runLlmHook(entry, payload, ctx, { system: ccAgentHookSystemFor(event), event });
1279
1324
  case "command":
1280
- return runCommandHook(entry, payload, ctx, halt);
1325
+ return runCommandHook(entry, payload, ctx, halt, event);
1281
1326
  default: {
1282
1327
  const unreachable = entry;
1283
1328
  void unreachable;
@@ -1285,6 +1330,41 @@ function runRunnableEntry(entry, payload, ctx, event, llmExtra, halt) {
1285
1330
  }
1286
1331
  }
1287
1332
  }
1333
+ async function settleOnce(spentOf, body) {
1334
+ const candidates = new Set();
1335
+ let refused = false;
1336
+ const once = {
1337
+ spent: (entry) => spentOf.has(entry),
1338
+ ran: (entry) => {
1339
+ if (entry.once)
1340
+ candidates.add(entry);
1341
+ },
1342
+ handedOff: (entry) => {
1343
+ if (entry.once)
1344
+ spentOf.add(entry);
1345
+ },
1346
+ unsettled: (entry) => {
1347
+ candidates.delete(entry);
1348
+ },
1349
+ refused: (answer) => {
1350
+ refused = true;
1351
+ return answer;
1352
+ },
1353
+ mark: () => {
1354
+ const at = new Set(candidates);
1355
+ return () => {
1356
+ candidates.clear();
1357
+ for (const entry of at)
1358
+ candidates.add(entry);
1359
+ };
1360
+ },
1361
+ };
1362
+ const answer = await body(once);
1363
+ if (!refused)
1364
+ for (const entry of candidates)
1365
+ spentOf.add(entry);
1366
+ return answer;
1367
+ }
1288
1368
  function isSyncEntry(entry) {
1289
1369
  return !(entry.type === "command" && (entry.async === true || entry.asyncRewake === true));
1290
1370
  }
@@ -1342,7 +1422,7 @@ function noteRanVerdictFlaw(ctx, ledger, event, ran, flaw, toolName) {
1342
1422
  }
1343
1423
  }
1344
1424
  }
1345
- async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired, halt, deadlineAt, ledger, llmExtra) {
1425
+ async function runMatchingEntries(event, groups, matchValue, payload, ctx, once, halt, deadlineAt, ledger, llmExtra) {
1346
1426
  const results = [];
1347
1427
  let llmEntries = 0;
1348
1428
  const toolName = toolNameOf(payload);
@@ -1350,10 +1430,10 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1350
1430
  const truncatedFor = (entry) => (entry.type === "prompt" || entry.type === "agent" ? llmHookArguments(payload).truncated : inputTruncated);
1351
1431
  const blockedBy = (entry, source, cause) => ({ entry, run: blockingFailureRun(event, cause), source });
1352
1432
  let laneSpent = false;
1353
- const settle = async (entry, onceKey) => {
1433
+ const settle = async (entry) => {
1354
1434
  if (halt.signal.aborted)
1355
1435
  return { kind: "halted" };
1356
- if (entry.once && onceFired.has(onceKey))
1436
+ if (entry.once && once.spent(entry))
1357
1437
  return { kind: "passed" };
1358
1438
  let ifOverBudget = false;
1359
1439
  if (entry.if !== undefined) {
@@ -1379,7 +1459,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1379
1459
  }
1380
1460
  if (entry.type === "command" && entry.shell === "powershell") {
1381
1461
  ctx.logger.warn("hook_entries_skipped", { event, type: entry.type, reason: "powershell_not_supported" });
1382
- return isSyncEntry(entry) ? { kind: "unrun", reason: "powershell_unsupported" } : { kind: "passed" };
1462
+ return { kind: "unrun", reason: "powershell_unsupported" };
1383
1463
  }
1384
1464
  if (entry.type === "prompt" || entry.type === "agent") {
1385
1465
  const carrier = entry.type === "prompt" ? ctx.hookLlm : ctx.hookAgent;
@@ -1393,8 +1473,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1393
1473
  }
1394
1474
  }
1395
1475
  if (entry.type === "command" && (entry.async || entry.asyncRewake)) {
1396
- if (entry.once)
1397
- onceFired.add(onceKey);
1476
+ once.handedOff(entry);
1398
1477
  void fireAsyncCommandHook(entry, payload, ctx, event, halt.signal);
1399
1478
  return { kind: "passed" };
1400
1479
  }
@@ -1403,8 +1482,6 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1403
1482
  ctx.logger.warn("hook_event_deadline_exceeded", { event, budgetSec: MAX_HOOK_EVENT_TOTAL_SECONDS });
1404
1483
  return { kind: "unrun", reason: "budget_exhausted" };
1405
1484
  }
1406
- if (entry.once)
1407
- onceFired.add(onceKey);
1408
1485
  if (entry.type === "prompt" || entry.type === "agent")
1409
1486
  llmEntries++;
1410
1487
  const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra, halt.signal);
@@ -1437,15 +1514,13 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1437
1514
  return { kind: "unrun", reason: "bad_json" };
1438
1515
  return ifOverBudget ? { kind: "ran", run, overBudget: true } : { kind: "ran", run };
1439
1516
  };
1440
- for (const { group, index } of groups) {
1517
+ for (const group of groups) {
1441
1518
  const pending = laneSpent ? group.hooks.filter((entry) => blocksOnFailure(entry, event)) : group.hooks;
1442
1519
  if (laneSpent && pending.length === 0)
1443
1520
  continue;
1444
1521
  const selected = matcherGroupSelected(ctx, event, group, matchValue, toolName, pending);
1445
1522
  if (selected === "invalid") {
1446
1523
  for (const entry of pending) {
1447
- if (!isSyncEntry(entry))
1448
- continue;
1449
1524
  if (!laneSpent)
1450
1525
  ledger.unrun(group.source, entry, "matcher_invalid");
1451
1526
  if (blocksOnFailure(entry, event))
@@ -1455,12 +1530,13 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1455
1530
  }
1456
1531
  if (selected === false)
1457
1532
  continue;
1458
- for (const [ei, entry] of group.hooks.entries()) {
1533
+ for (const entry of group.hooks) {
1459
1534
  if (laneSpent && !blocksOnFailure(entry, event))
1460
1535
  continue;
1461
- const fate = await settle(entry, `${event}:${index}:${ei}`);
1536
+ const fate = await settle(entry);
1462
1537
  switch (fate.kind) {
1463
1538
  case "ran": {
1539
+ once.ran(entry);
1464
1540
  const flaw = blocksOnFailure(entry, event) ? ranVerdictFlaw(event, fate.run) : undefined;
1465
1541
  if (flaw !== undefined) {
1466
1542
  noteRanVerdictFlaw(ctx, ledger, event, { entry, run: fate.run, source: group.source }, flaw, toolName);
@@ -1490,7 +1566,7 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1490
1566
  case "passed":
1491
1567
  break;
1492
1568
  case "halted":
1493
- if (isSyncEntry(entry) && halt.budgetSpent())
1569
+ if (halt.budgetSpent())
1494
1570
  ledger.halted(group.source, entry);
1495
1571
  return results;
1496
1572
  default: {
@@ -1508,17 +1584,16 @@ function createEventClock() {
1508
1584
  }
1509
1585
  const ENGINE_BUDGET_SPENT_DETAIL = "the managed lane's handler ran past its engine budget (HOOKS_TIMEOUT_MS); the managed entries after this one did not run on this event";
1510
1586
  function hookPhasesOf(groups) {
1511
- const indexed = groups.map((group, index) => ({ group, index, phase: hookPhaseOf(group.source) }));
1512
- return HOOK_PHASES.map((phase) => ({ phase, groups: indexed.filter((g) => g.phase === phase).map(({ group, index }) => ({ group, index })) })).filter((l) => l.groups.length > 0);
1587
+ return HOOK_PHASES.map((phase) => ({ phase, groups: groups.filter((g) => hookPhaseOf(g.source) === phase) })).filter((l) => l.groups.length > 0);
1513
1588
  }
1514
1589
  function trustRank(phase) {
1515
1590
  return HOOK_PHASES.length - 1 - HOOK_PHASES.indexOf(phase);
1516
1591
  }
1517
- async function runEventLanes(event, groups, matchValue, payload, ctx, onceFired, halt, ledger, llmExtra) {
1592
+ async function runEventLanes(event, groups, matchValue, payload, ctx, once, halt, ledger, llmExtra) {
1518
1593
  const deadline = createEventClock();
1519
1594
  const ran = [];
1520
1595
  for (const lane of hookPhasesOf(groups)) {
1521
- for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, onceFired, halt, deadline(), ledger, llmExtra))
1596
+ for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, once, halt, deadline(), ledger, llmExtra))
1522
1597
  ran.push({ ...r, phase: lane.phase });
1523
1598
  }
1524
1599
  return ran.sort((a, b) => trustRank(a.phase) - trustRank(b.phase)).map(({ entry, run, source }) => ({ entry, run, source }));
@@ -1534,9 +1609,9 @@ function basePayload(ctx, permissionMode) {
1534
1609
  function ignoreMatchers(groups) {
1535
1610
  return groups.map((g) => (g.matcher === undefined ? g : { ...g, matcher: undefined }));
1536
1611
  }
1537
- async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, onceFired, halt) {
1538
- const ledger = createUnrunLedger(ctx, event, toolNameOf(payload));
1539
- const singles = await runEventLanes(event, groups, matchValue, payload, ctx, onceFired, halt, ledger);
1612
+ async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, once, halt) {
1613
+ const ledger = createUnrunLedger(ctx, event, toolNameOf(payload), once);
1614
+ const singles = await runEventLanes(event, groups, matchValue, payload, ctx, once, halt, ledger);
1540
1615
  for (const ran of singles) {
1541
1616
  const { run } = ran;
1542
1617
  if (run.code !== 0) {
@@ -1589,17 +1664,16 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1589
1664
  if (!pre && !post && !postFailure && !postBatch && !promptSubmit && !stopEntries && !stopFailureEntries && !preCompactEntries && !postCompactEntries) {
1590
1665
  return undefined;
1591
1666
  }
1592
- const onceFired = new Set();
1667
+ const onceSpent = new Set();
1593
1668
  const translators = {};
1594
1669
  if (pre) {
1595
- const tightenOnly = managedTightenOnlyOn(lane, "classic.PreToolUse", ctx.belowTaskChainPatterns);
1596
- translators.PreToolUse = async (e, halt, below) => {
1670
+ const tightenOnly = chainsBelowTaskOn(ctx.belowTaskChainPatterns, "classic.PreToolUse");
1671
+ translators.PreToolUse = (e, halt, below) => settleOnce(onceSpent, async (once) => {
1597
1672
  const prior = readBelowPreToolUse(below);
1598
- const realApproval = prior.realApproval ? { requiresRealApproval: true } : {};
1599
1673
  if (prior.denied)
1600
1674
  return undefined;
1601
1675
  const toolName = e.tool;
1602
- const ledger = createUnrunLedger(ctx, "PreToolUse", toolName);
1676
+ const ledger = createUnrunLedger(ctx, "PreToolUse", toolName, once);
1603
1677
  const deadline = createEventClock();
1604
1678
  let currentInput = prior.updatedInput ?? e.input;
1605
1679
  let rewritten = prior.updatedInput !== undefined;
@@ -1609,9 +1683,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1609
1683
  let unknownDecision = false;
1610
1684
  const contexts = { unmanaged: [], managedPlugin: [], managedPolicy: [] };
1611
1685
  const contextField = () => buildHookContextField([...[...HOOK_PHASES].sort((a, b) => trustRank(a) - trustRank(b)).flatMap((p) => contexts[p]), ...(prior.additionalContext !== undefined ? [prior.additionalContext] : [])]);
1612
- const runPass = async (phase, groups, once) => {
1686
+ const strictness = prior.realApproval === true ? { requiresRealApproval: true } : {};
1687
+ const runPass = async (phase, groups) => {
1613
1688
  let rewroteBy;
1614
- for (const laneGroup of groups) {
1689
+ for (const group of groups) {
1615
1690
  const groupInput = currentInput;
1616
1691
  const payload = {
1617
1692
  ...basePayload(ctx, e.permissionMode),
@@ -1620,7 +1695,7 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1620
1695
  tool_input: groupInput,
1621
1696
  tool_use_id: e.tool_use_id,
1622
1697
  };
1623
- const singles = await runMatchingEntries("PreToolUse", [laneGroup], toolName, payload, ctx, once, halt, deadline(), ledger);
1698
+ const singles = await runMatchingEntries("PreToolUse", [group], toolName, payload, ctx, once, halt, deadline(), ledger);
1624
1699
  for (const ran of singles) {
1625
1700
  const { entry, run } = ran;
1626
1701
  if (run.code === 2) {
@@ -1659,10 +1734,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1659
1734
  const proposed = h.updatedInput !== null && typeof h.updatedInput === "object" ? h.updatedInput : undefined;
1660
1735
  const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
1661
1736
  if (tightenOnly && ran.distrust === undefined && (decision === "allow" || (proposesRewrite && (decision === "ask" || decision === undefined)))) {
1662
- return { deny: { action: "deny", message: proposesRewrite ? MANAGED_REWRITE_UNSHOWN_REASON : MANAGED_ALLOW_UNJUDGED_REASON, ...contextField() } };
1737
+ return { deny: { action: "deny", message: proposesRewrite ? REQUEST_HOOK_REWRITE_UNSHOWN_REASON : REQUEST_HOOK_ALLOW_UNJUDGED_REASON, ...contextField() } };
1663
1738
  }
1664
1739
  if (decision === "ask") {
1665
- asked = alsoAsks(prior.asked ?? prior.unreadable?.message, clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS));
1740
+ asked = alsoAsks(lane, prior.asked ?? prior.unreadable?.message, clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS));
1666
1741
  spoke = true;
1667
1742
  }
1668
1743
  if (decision === "allow" || decision === undefined) {
@@ -1694,54 +1769,52 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1694
1769
  };
1695
1770
  for (const lane of hookPhasesOf(pre)) {
1696
1771
  if (lane.phase === "unmanaged") {
1697
- const pass = await runPass(lane.phase, lane.groups, onceFired);
1772
+ const pass = await runPass(lane.phase, lane.groups);
1698
1773
  if (pass.deny !== undefined)
1699
- return pass.deny;
1774
+ return once.refused(pass.deny);
1700
1775
  continue;
1701
1776
  }
1702
- const onceBefore = new Set(onceFired);
1703
- const first = await runPass(lane.phase, lane.groups, onceFired);
1777
+ const rollback = once.mark();
1778
+ const first = await runPass(lane.phase, lane.groups);
1704
1779
  if (first.deny !== undefined)
1705
- return first.deny;
1780
+ return once.refused(first.deny);
1706
1781
  if (first.rewroteBy === undefined)
1707
1782
  continue;
1708
1783
  contexts[lane.phase].length = 0;
1709
- const rejudgeOnce = new Set(onceBefore);
1710
- const second = await runPass(lane.phase, lane.groups, rejudgeOnce);
1711
- for (const key of rejudgeOnce)
1712
- onceFired.add(key);
1784
+ rollback();
1785
+ const second = await runPass(lane.phase, lane.groups);
1713
1786
  if (second.deny !== undefined)
1714
- return second.deny;
1787
+ return once.refused(second.deny);
1715
1788
  if (second.rewroteBy !== undefined) {
1716
1789
  ledger.unrun(second.rewroteBy.source, second.rewroteBy.entry, "rewrite_unsettled", "the managed hooks of this phase rewrote the tool input again after being re-run on their own rewrite; approval required");
1717
1790
  }
1718
1791
  }
1719
1792
  const managedGap = ledger.managedVerdictGap;
1720
- if (below !== undefined && !spoke && managedGap === undefined)
1793
+ if (!spoke && managedGap === undefined)
1721
1794
  return undefined;
1722
1795
  if (asked !== undefined) {
1723
- return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
1796
+ return { action: "ask", message: asked, questionCause: "asked", ...strictness, ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1724
1797
  }
1725
1798
  if (managedGap !== undefined) {
1726
1799
  return {
1727
1800
  action: "ask",
1728
1801
  message: "a managed PreToolUse hook did not run to a verdict — approval required",
1729
1802
  questionCause: managedGap,
1803
+ ...strictness,
1730
1804
  ...(rewritten ? { updatedInput: currentInput } : {}),
1731
- ...realApproval,
1732
1805
  ...contextField(),
1733
1806
  };
1734
1807
  }
1735
1808
  if (prior.unreadable !== undefined) {
1736
- return { action: "ask", message: prior.unreadable.message, questionCause: prior.unreadable.cause, ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
1809
+ return { action: "ask", message: prior.unreadable.message, questionCause: prior.unreadable.cause, ...strictness, ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1737
1810
  }
1738
1811
  if (unknownDecision) {
1739
1812
  return {
1740
1813
  action: "ask",
1741
1814
  message: "a PreToolUse hook returned an unrecognized permissionDecision — approval required",
1742
1815
  questionCause: "verdict_unreadable",
1816
+ ...strictness,
1743
1817
  ...(rewritten ? { updatedInput: currentInput } : {}),
1744
- ...realApproval,
1745
1818
  ...contextField(),
1746
1819
  };
1747
1820
  }
@@ -1751,10 +1824,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1751
1824
  if (rewritten || HOOK_PHASES.some((p) => contexts[p].length > 0) || prior.additionalContext !== undefined)
1752
1825
  return carried;
1753
1826
  return undefined;
1754
- };
1827
+ });
1755
1828
  }
1756
1829
  if (post) {
1757
- translators.PostToolUse = async (e, halt, below) => {
1830
+ translators.PostToolUse = (e, halt, below) => settleOnce(onceSpent, async (once) => {
1758
1831
  const toolName = e.tool;
1759
1832
  const input = e.input;
1760
1833
  const output = e.output;
@@ -1770,8 +1843,8 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1770
1843
  tool_response: { isError: output.isError, text: clipDataText(responseText, MAX_TOOL_RESPONSE_CHARS) },
1771
1844
  tool_use_id: e.tool_use_id,
1772
1845
  };
1773
- const ledger = createUnrunLedger(ctx, "PostToolUse", toolName);
1774
- const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, onceFired, halt, ledger);
1846
+ const ledger = createUnrunLedger(ctx, "PostToolUse", toolName, once);
1847
+ const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, once, halt, ledger);
1775
1848
  for (const ran of singles) {
1776
1849
  const { entry, run } = ran;
1777
1850
  if (run.code === 2) {
@@ -1807,10 +1880,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1807
1880
  if (contexts.length === 0)
1808
1881
  return undefined;
1809
1882
  return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1810
- };
1883
+ });
1811
1884
  }
1812
1885
  if (postFailure) {
1813
- translators.PostToolUseFailure = async (e, halt, below) => {
1886
+ translators.PostToolUseFailure = (e, halt, below) => settleOnce(onceSpent, async (once) => {
1814
1887
  const toolName = e.tool;
1815
1888
  const f = e.failure;
1816
1889
  const contexts = [];
@@ -1823,8 +1896,8 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1823
1896
  error: clipDataText(f.error, MAX_TOOL_RESPONSE_CHARS),
1824
1897
  is_interrupt: f.isInterrupt,
1825
1898
  };
1826
- const ledger = createUnrunLedger(ctx, "PostToolUseFailure", toolName);
1827
- const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired, halt, ledger);
1899
+ const ledger = createUnrunLedger(ctx, "PostToolUseFailure", toolName, once);
1900
+ const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, once, halt, ledger);
1828
1901
  for (const ran of singles) {
1829
1902
  const { entry, run } = ran;
1830
1903
  if (run.code === 2) {
@@ -1856,10 +1929,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1856
1929
  if (contexts.length === 0)
1857
1930
  return undefined;
1858
1931
  return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1859
- };
1932
+ });
1860
1933
  }
1861
1934
  if (postBatch) {
1862
- translators.PostToolBatch = async (e, halt, below) => {
1935
+ translators.PostToolBatch = (e, halt, below) => settleOnce(onceSpent, async (once) => {
1863
1936
  const contexts = [];
1864
1937
  const payload = {
1865
1938
  ...basePayload(ctx, e.permissionMode),
@@ -1871,8 +1944,8 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1871
1944
  tool_response: { isError: c.isError, ...(c.response !== undefined ? { text: clipDataText(c.response, MAX_TOOL_RESPONSE_CHARS) } : {}) },
1872
1945
  })),
1873
1946
  };
1874
- const ledger = createUnrunLedger(ctx, "PostToolBatch", undefined);
1875
- const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, onceFired, halt, ledger);
1947
+ const ledger = createUnrunLedger(ctx, "PostToolBatch", undefined, once);
1948
+ const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, once, halt, ledger);
1876
1949
  for (const ran of singles) {
1877
1950
  const { entry, run } = ran;
1878
1951
  if (run.code === 2) {
@@ -1900,19 +1973,19 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1900
1973
  if (contexts.length === 0)
1901
1974
  return undefined;
1902
1975
  return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1903
- };
1976
+ });
1904
1977
  }
1905
1978
  if (promptSubmit) {
1906
- translators.UserPromptSubmit = async (e, halt, below) => {
1979
+ translators.UserPromptSubmit = (e, halt, below) => settleOnce(onceSpent, async (once) => {
1907
1980
  const contexts = [];
1908
1981
  const payload = { ...basePayload(ctx, e.permissionMode), hook_event_name: "UserPromptSubmit", prompt: clipDataText(e.prompt, MAX_TOOL_INPUT_CHARS) };
1909
- const ledger = createUnrunLedger(ctx, "UserPromptSubmit", undefined);
1910
- const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, halt, ledger);
1982
+ const ledger = createUnrunLedger(ctx, "UserPromptSubmit", undefined, once);
1983
+ const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, once, halt, ledger);
1911
1984
  for (const ran of singles) {
1912
1985
  const { entry, run } = ran;
1913
1986
  if (run.code === 2) {
1914
1987
  const reason = clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a UserPromptSubmit hook";
1915
- return { block: reason, ...buildHookContextField(contexts) };
1988
+ return once.refused({ block: reason, ...buildHookContextField(contexts) });
1916
1989
  }
1917
1990
  if (run.code !== 0) {
1918
1991
  noteExitNonzero(ctx, ledger, "UserPromptSubmit", ran);
@@ -1925,11 +1998,11 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1925
1998
  logHookSystemMessage(ctx, "UserPromptSubmit", out.systemMessage);
1926
1999
  if (out.continue === false) {
1927
2000
  const reason = clipDataText(typeof out.stopReason === "string" ? out.stopReason : "hook requested stop", MAX_HOOK_FEEDBACK_CHARS);
1928
- return { block: reason, ...buildHookContextField(contexts) };
2001
+ return once.refused({ block: reason, ...buildHookContextField(contexts) });
1929
2002
  }
1930
2003
  if (out.decision === "block") {
1931
2004
  const reason = clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a UserPromptSubmit hook", MAX_HOOK_FEEDBACK_CHARS);
1932
- return { block: reason, ...buildHookContextField(contexts) };
2005
+ return once.refused({ block: reason, ...buildHookContextField(contexts) });
1933
2006
  }
1934
2007
  const hso = out.hookSpecificOutput;
1935
2008
  if (hso !== null && typeof hso === "object" && hso.hookEventName === "UserPromptSubmit") {
@@ -1941,10 +2014,10 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1941
2014
  if (contexts.length === 0)
1942
2015
  return undefined;
1943
2016
  return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1944
- };
2017
+ });
1945
2018
  }
1946
2019
  if (stopEntries) {
1947
- translators.Stop = async (sctx, halt, below) => {
2020
+ translators.Stop = (sctx, halt, below) => settleOnce(onceSpent, async (once) => {
1948
2021
  const payload = { ...basePayload(ctx, sctx.permissionMode), hook_event_name: "Stop", stop_hook_active: sctx.stopHookActive };
1949
2022
  let llmExtra;
1950
2023
  const wantsPrompt = stopEntries.some((g) => g.hooks.some((h) => h.type === "prompt"));
@@ -1972,7 +2045,7 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1972
2045
  }
1973
2046
  }
1974
2047
  const stopGroups = ignoreMatchers(stopEntries);
1975
- const ledger = createUnrunLedger(ctx, "Stop", undefined);
2048
+ const ledger = createUnrunLedger(ctx, "Stop", undefined, once);
1976
2049
  if (!llmExtra && wantsPrompt) {
1977
2050
  for (const g of stopGroups)
1978
2051
  for (const h of g.hooks)
@@ -1982,7 +2055,7 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
1982
2055
  const effectiveGroups = llmExtra || !wantsPrompt
1983
2056
  ? stopGroups
1984
2057
  : stopGroups.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.type !== "prompt") })).filter((g) => g.hooks.length > 0);
1985
- const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, onceFired, halt, ledger, llmExtra);
2058
+ const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, once, halt, ledger, llmExtra);
1986
2059
  let block;
1987
2060
  let sawContinueFalse = false;
1988
2061
  const contexts = [];
@@ -2036,15 +2109,16 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
2036
2109
  return undefined;
2037
2110
  if (block === undefined && contexts.length === 0)
2038
2111
  return undefined;
2039
- return {
2112
+ const answer = {
2040
2113
  ...belowMembers(below),
2041
2114
  ...(block !== undefined ? { block } : {}),
2042
2115
  ...buildHookContextField(withBelowContext(contexts, below)),
2043
2116
  };
2044
- };
2117
+ return block !== undefined ? once.refused(answer) : answer;
2118
+ });
2045
2119
  }
2046
2120
  if (stopFailureEntries) {
2047
- translators.StopFailure = async (fctx, halt) => {
2121
+ translators.StopFailure = (fctx, halt) => settleOnce(onceSpent, async (once) => {
2048
2122
  const turnError = toAssistantTurnError(fctx.errorKind);
2049
2123
  const payload = {
2050
2124
  ...basePayload(ctx, undefined),
@@ -2052,23 +2126,23 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
2052
2126
  error: turnError,
2053
2127
  ...(fctx.error ? { error_details: clipDataText(redactSecrets(fctx.error), MAX_HOOK_FEEDBACK_CHARS) } : {}),
2054
2128
  };
2055
- await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired, halt);
2056
- };
2129
+ await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, once, halt);
2130
+ });
2057
2131
  }
2058
2132
  if (preCompactEntries) {
2059
- translators.PreCompact = async (pctx, halt, below) => {
2133
+ translators.PreCompact = (pctx, halt, below) => settleOnce(onceSpent, async (once) => {
2060
2134
  const payload = {
2061
2135
  ...basePayload(ctx, undefined),
2062
2136
  hook_event_name: "PreCompact",
2063
2137
  trigger: pctx.trigger,
2064
2138
  custom_instructions: pctx.customInstructions ?? null,
2065
2139
  };
2066
- const ledger = createUnrunLedger(ctx, "PreCompact", undefined);
2067
- const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired, halt, ledger);
2140
+ const ledger = createUnrunLedger(ctx, "PreCompact", undefined, once);
2141
+ const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, once, halt, ledger);
2068
2142
  for (const ran of singles) {
2069
2143
  const { entry, run } = ran;
2070
2144
  if (run.code === 2) {
2071
- return { ...belowMembers(below), block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
2145
+ return once.refused({ ...belowMembers(below), block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" });
2072
2146
  }
2073
2147
  if (run.code !== 0) {
2074
2148
  noteExitNonzero(ctx, ledger, "PreCompact", ran);
@@ -2083,31 +2157,31 @@ function createLaneRegistration(lane, config, ctx, budgetMs) {
2083
2157
  ctx.logger.warn("hook_continue_false_unsupported", { event: "PreCompact" });
2084
2158
  }
2085
2159
  if (out.decision === "block") {
2086
- return { ...belowMembers(below), block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) };
2160
+ return once.refused({ ...belowMembers(below), block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) });
2087
2161
  }
2088
2162
  }
2089
2163
  return undefined;
2090
- };
2164
+ });
2091
2165
  }
2092
2166
  if (postCompactEntries) {
2093
- translators.PostCompact = async (cctx, halt) => {
2167
+ translators.PostCompact = (cctx, halt) => settleOnce(onceSpent, async (once) => {
2094
2168
  const payload = {
2095
2169
  ...basePayload(ctx, undefined),
2096
2170
  hook_event_name: "PostCompact",
2097
2171
  trigger: cctx.trigger,
2098
2172
  compact_summary: clipDataText(cctx.summary, MAX_TOOL_RESPONSE_CHARS),
2099
2173
  };
2100
- await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired, halt);
2101
- };
2174
+ await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, once, halt);
2175
+ });
2102
2176
  }
2103
2177
  const events = Object.keys(translators);
2104
2178
  return {
2105
2179
  name: SETTINGS_HOOKS_REGISTRATIONS[lane],
2106
- uses: { events: events.flatMap((event) => SETTINGS_HOOK_STATIONS[event]), calls: [] },
2180
+ uses: { events: events.map((event) => SETTINGS_HOOK_STATIONS[event]), calls: [] },
2107
2181
  budgetMs,
2108
2182
  register(on) {
2109
2183
  for (const event of events)
2110
- hangTranslator(on, event, translators, lane);
2184
+ hangTranslator(on, event, translators);
2111
2185
  },
2112
2186
  };
2113
2187
  }