@sema-agent/server 7.145.0 → 7.147.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 (526) hide show
  1. package/MIGRATION.md +876 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +374 -99
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +61 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/README.md +2 -2
  14. package/deploy/sema-up/chart/templates/_helpers.tpl +41 -0
  15. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  16. package/deploy/sema-up/chart/templates/server-v4pro.yaml +9 -4
  17. package/deploy/sema-up/chart/templates/server.yaml +9 -4
  18. package/deploy/sema-up/chart/values.yaml +8 -1
  19. package/deploy/sema-up/cluster-up.sh +2 -1
  20. package/deploy/sema-up/lib/flagshape.sh +4 -0
  21. package/deploy/sema-up/lib/lanes.json +2 -5
  22. package/deploy/sema-up/sema-up.sh +12 -9
  23. package/deploy/sema-up/stack/compose.yaml +5 -4
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +34 -17
  25. package/dist/adoption/import-limits.d.ts +6 -0
  26. package/dist/adoption/import-limits.js +7 -0
  27. package/dist/adoption/import.d.ts +62 -0
  28. package/dist/adoption/import.js +107 -0
  29. package/dist/adoption/wire.d.ts +52 -0
  30. package/dist/adoption/wire.js +9 -0
  31. package/dist/approval-ask-machine.d.ts +2 -2
  32. package/dist/approval-card.d.ts +7 -16
  33. package/dist/approval-card.js +0 -4
  34. package/dist/approval-gate-currency.d.ts +1 -1
  35. package/dist/approval-gate-currency.js +1 -1
  36. package/dist/approval-reconciler.d.ts +1 -1
  37. package/dist/approval.d.ts +3 -3
  38. package/dist/audit.js +2 -2
  39. package/dist/bench/s1/live-deps.js +3 -1
  40. package/dist/boot/crash-last.js +2 -1
  41. package/dist/boot/deployment-mods.d.ts +11 -1
  42. package/dist/boot/deployment-mods.js +15 -2
  43. package/dist/boot/device-lane.js +7 -5
  44. package/dist/boot/engine-lease.d.ts +195 -22
  45. package/dist/boot/engine-lease.js +146 -20
  46. package/dist/boot/execution-env.d.ts +5 -0
  47. package/dist/boot/execution-env.js +57 -33
  48. package/dist/boot/execution-lane.d.ts +37 -9
  49. package/dist/boot/execution-lane.js +74 -20
  50. package/dist/boot/exit-boundary.d.ts +50 -0
  51. package/dist/boot/exit-boundary.js +32 -0
  52. package/dist/boot/exit-last.d.ts +20 -4
  53. package/dist/boot/exit-last.js +23 -5
  54. package/dist/boot/governance-seams.d.ts +2 -2
  55. package/dist/boot/governance-seams.js +1 -0
  56. package/dist/boot/leader.js +1 -1
  57. package/dist/boot/memory-boundary.d.ts +11 -11
  58. package/dist/boot/memory-boundary.js +3 -3
  59. package/dist/boot/memory-consolidation.d.ts +42 -41
  60. package/dist/boot/memory-consolidation.js +26 -28
  61. package/dist/boot/org-memory.js +3 -2
  62. package/dist/boot/parked-revive-gate.d.ts +1 -1
  63. package/dist/boot/parked-revive-gate.js +1 -1
  64. package/dist/boot/reapers.d.ts +12 -4
  65. package/dist/boot/reapers.js +40 -16
  66. package/dist/boot/resolve-spec.d.ts +2 -0
  67. package/dist/boot/resolve-spec.js +126 -123
  68. package/dist/boot/retention-lane.d.ts +4 -42
  69. package/dist/boot/runner-deps.js +1 -1
  70. package/dist/boot/session-faces.d.ts +1 -0
  71. package/dist/boot/session-faces.js +11 -3
  72. package/dist/boot/shutdown.js +27 -9
  73. package/dist/boot/stage-01-config.d.ts +1 -1
  74. package/dist/boot/stage-01-config.js +7 -3
  75. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -1
  76. package/dist/boot/stage-02-web-admin-pull.js +10 -8
  77. package/dist/boot/stage-03-store-backend.d.ts +2 -1
  78. package/dist/boot/stage-03-store-backend.js +2 -2
  79. package/dist/boot/stage-04-stores.d.ts +2 -1
  80. package/dist/boot/stage-05-execution-env.d.ts +4 -1
  81. package/dist/boot/stage-05-execution-env.js +6 -6
  82. package/dist/boot/stage-06-runners.d.ts +5 -2
  83. package/dist/boot/stage-06-runners.js +12 -13
  84. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  85. package/dist/boot/stage-07-capability-layer.js +7 -7
  86. package/dist/boot/stage-08-reapers.d.ts +5 -2
  87. package/dist/boot/stage-08-reapers.js +3 -3
  88. package/dist/boot/stage-09-leader.d.ts +6 -2
  89. package/dist/boot/stage-10-http-server.d.ts +6 -2
  90. package/dist/boot/stage-10-http-server.js +5 -3
  91. package/dist/boot/stores.d.ts +10 -0
  92. package/dist/boot/stores.js +53 -26
  93. package/dist/boot/web-admin.js +3 -3
  94. package/dist/boot/workflow-orchestration.d.ts +1 -1
  95. package/dist/boot/workflow-orchestration.js +2 -1
  96. package/dist/capabilities/center-plugins.d.ts +2 -0
  97. package/dist/capabilities/center-plugins.js +6 -2
  98. package/dist/capabilities/composite-members.d.ts +4 -1
  99. package/dist/capabilities/composite-members.js +4 -1
  100. package/dist/capabilities/execution-lane.d.ts +7 -7
  101. package/dist/capabilities/execution-lane.js +2 -2
  102. package/dist/capabilities/hands-lane.d.ts +1 -1
  103. package/dist/capabilities/scenarios.d.ts +34 -20
  104. package/dist/capabilities/scenarios.js +13 -9
  105. package/dist/capabilities/settings-contract.d.ts +29 -0
  106. package/dist/capabilities/settings-contract.js +14 -0
  107. package/dist/capabilities/skills.d.ts +5 -5
  108. package/dist/capability-facts.d.ts +56 -1
  109. package/dist/capability-facts.js +59 -8
  110. package/dist/center-credential-renewer.js +3 -2
  111. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  112. package/dist/config-catalog.js +19 -9
  113. package/dist/config-invariants.d.ts +18 -3
  114. package/dist/config-invariants.js +36 -19
  115. package/dist/config-lkg.d.ts +1 -1
  116. package/dist/config-lkg.js +5 -6
  117. package/dist/config-provider.d.ts +2 -2
  118. package/dist/config-provider.js +2 -0
  119. package/dist/config-types.d.ts +228 -185
  120. package/dist/config.d.ts +74 -6
  121. package/dist/config.js +146 -64
  122. package/dist/degenerate-instrument.js +1 -1
  123. package/dist/deleted-session-ids.d.ts +19 -0
  124. package/dist/deleted-session-ids.js +26 -0
  125. package/dist/deploy-facts.d.ts +155 -0
  126. package/dist/deploy-facts.js +95 -0
  127. package/dist/deployment-shape.d.ts +21 -12
  128. package/dist/deployment-shape.js +24 -6
  129. package/dist/device-ws-hub.d.ts +48 -3
  130. package/dist/device-ws-hub.js +48 -2
  131. package/dist/env-facts.d.ts +7 -5
  132. package/dist/env-facts.js +10 -6
  133. package/dist/error-code-family.d.ts +35 -0
  134. package/dist/error-code-family.js +26 -0
  135. package/dist/execution-config-root.d.ts +5 -5
  136. package/dist/execution-config-root.js +5 -5
  137. package/dist/execution-env-purpose.d.ts +14 -0
  138. package/dist/execution-env-purpose.js +29 -0
  139. package/dist/execution-lane-caps.d.ts +49 -86
  140. package/dist/execution-lane-caps.js +52 -62
  141. package/dist/execution-placement.d.ts +18 -2
  142. package/dist/execution-placement.js +19 -0
  143. package/dist/file-history-disclosure.d.ts +2 -2
  144. package/dist/fleet/drain-entry.d.ts +46 -3
  145. package/dist/fleet/drain-entry.js +6 -0
  146. package/dist/fleet/fleet-bus.d.ts +2 -1
  147. package/dist/fleet/serving-lease.js +3 -4
  148. package/dist/history-anchor.d.ts +22 -0
  149. package/dist/history-anchor.js +14 -0
  150. package/dist/hooks/branch-transcript.d.ts +1 -1
  151. package/dist/hooks/branch-transcript.js +1 -0
  152. package/dist/hooks/classic-stations.d.ts +25 -8
  153. package/dist/hooks/classic-stations.js +29 -3
  154. package/dist/hooks/command-spawn.d.ts +1 -4
  155. package/dist/hooks/command-spawn.js +0 -11
  156. package/dist/hooks/hook-runner.d.ts +85 -24
  157. package/dist/hooks/hook-runner.js +326 -109
  158. package/dist/hooks/hook-source.d.ts +14 -2
  159. package/dist/hooks/hook-source.js +8 -0
  160. package/dist/http/active-run-conflict.d.ts +1 -1
  161. package/dist/http/active-run-conflict.js +1 -1
  162. package/dist/http/admission.js +11 -1
  163. package/dist/http/approval-sweeps.d.ts +2 -5
  164. package/dist/http/approval-sweeps.js +3 -2
  165. package/dist/http/checkpoint-ctx.d.ts +76 -71
  166. package/dist/http/checkpoint-ctx.js +40 -40
  167. package/dist/http/claim-latency.d.ts +10 -0
  168. package/dist/http/claim-latency.js +12 -0
  169. package/dist/http/dispatch.js +19 -3
  170. package/dist/http/durable-run-leg.d.ts +5 -0
  171. package/dist/http/durable-run-leg.js +3 -1
  172. package/dist/http/execution-context.d.ts +7 -1
  173. package/dist/http/execution-context.js +13 -0
  174. package/dist/http/health-self-report.d.ts +6 -2
  175. package/dist/http/health-self-report.js +3 -2
  176. package/dist/http/memory-compliance-failure-reply.d.ts +2 -2
  177. package/dist/http/memory-compliance-failure-reply.js +5 -15
  178. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  179. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  180. package/dist/http/memory-store-refusal-reply.d.ts +3 -3
  181. package/dist/http/memory-store-refusal-reply.js +3 -0
  182. package/dist/http/owner-access.d.ts +11 -0
  183. package/dist/http/owner-access.js +7 -4
  184. package/dist/http/principal-gate.d.ts +28 -16
  185. package/dist/http/principal-gate.js +10 -19
  186. package/dist/http/request-guards.d.ts +2 -2
  187. package/dist/http/request-guards.js +5 -5
  188. package/dist/http/resume-legs.d.ts +29 -10
  189. package/dist/http/resume-legs.js +94 -56
  190. package/dist/http/rewind-files-reply.d.ts +34 -0
  191. package/dist/http/rewind-files-reply.js +36 -0
  192. package/dist/http/route-ctx.d.ts +43 -26
  193. package/dist/http/route-table.js +4 -0
  194. package/dist/http/routes/adoption.d.ts +11 -0
  195. package/dist/http/routes/adoption.js +80 -6
  196. package/dist/http/routes/agents-roster.js +1 -0
  197. package/dist/http/routes/approvals-assistant.js +7 -26
  198. package/dist/http/routes/background.js +1 -0
  199. package/dist/http/routes/capabilities.js +12 -3
  200. package/dist/http/routes/devices.js +1 -1
  201. package/dist/http/routes/diagnostics.d.ts +9 -14
  202. package/dist/http/routes/diagnostics.js +10 -20
  203. package/dist/http/routes/engine.d.ts +31 -0
  204. package/dist/http/routes/engine.js +81 -0
  205. package/dist/http/routes/memory-bundle.js +3 -0
  206. package/dist/http/routes/memory-consolidation.js +3 -5
  207. package/dist/http/routes/memory-origin.d.ts +2 -2
  208. package/dist/http/routes/memory-origin.js +2 -4
  209. package/dist/http/routes/memory-policy.js +5 -1
  210. package/dist/http/routes/notify-wake.js +2 -1
  211. package/dist/http/routes/observability.js +3 -6
  212. package/dist/http/routes/projects.d.ts +70 -0
  213. package/dist/http/routes/projects.js +86 -0
  214. package/dist/http/routes/rules.js +30 -2
  215. package/dist/http/routes/runs.js +15 -11
  216. package/dist/http/routes/session-sync.js +39 -35
  217. package/dist/http/routes/sessions-list.d.ts +1 -1
  218. package/dist/http/routes/sessions.d.ts +7 -1
  219. package/dist/http/routes/sessions.js +108 -2
  220. package/dist/http/routes/tasks.js +32 -8
  221. package/dist/http/routes/trace-usage.js +1 -1
  222. package/dist/http/run-concurrency-cap.d.ts +86 -0
  223. package/dist/http/run-concurrency-cap.js +54 -0
  224. package/dist/http/server.d.ts +31 -14
  225. package/dist/http/server.js +29 -3
  226. package/dist/http/stored-body-replay.d.ts +12 -7
  227. package/dist/http/stored-body-replay.js +28 -5
  228. package/dist/http/wire-types.d.ts +27 -13
  229. package/dist/index.d.ts +2 -2
  230. package/dist/index.js +1 -1
  231. package/dist/instruction-source-notice.d.ts +1 -1
  232. package/dist/json-body-limit.d.ts +8 -0
  233. package/dist/json-body-limit.js +2 -0
  234. package/dist/leader/diffout.d.ts +1 -1
  235. package/dist/local-data-root.d.ts +2 -2
  236. package/dist/memory-embedder-reembed.d.ts +38 -0
  237. package/dist/memory-embedder-reembed.js +20 -0
  238. package/dist/memory-incident-sink.d.ts +0 -28
  239. package/dist/memory-incident-sink.js +5 -0
  240. package/dist/memory-operator-faces.d.ts +9 -4
  241. package/dist/memory-scope-stores.d.ts +9 -7
  242. package/dist/memory-scope-stores.js +3 -14
  243. package/dist/memory-scope.d.ts +38 -2
  244. package/dist/memory-scope.js +33 -2
  245. package/dist/memory-store-refusal.d.ts +17 -4
  246. package/dist/memory-store-refusal.js +1 -1
  247. package/dist/memory-sync-client.d.ts +2 -0
  248. package/dist/memory-sync-client.js +13 -11
  249. package/dist/memory-sync.js +1 -1
  250. package/dist/model-compat.d.ts +35 -21
  251. package/dist/model-compat.js +49 -21
  252. package/dist/model-entry-refusal.d.ts +11 -1
  253. package/dist/model-entry-refusal.js +7 -0
  254. package/dist/namespaced-rule-name.d.ts +18 -1
  255. package/dist/namespaced-rule-name.js +12 -0
  256. package/dist/net-address.d.ts +53 -0
  257. package/dist/net-address.js +111 -0
  258. package/dist/observability/fail-open.d.ts +29 -17
  259. package/dist/observability/fail-open.js +29 -17
  260. package/dist/observability/metrics.d.ts +2 -0
  261. package/dist/observability/metrics.js +2 -0
  262. package/dist/observability/secret-env-scrub.d.ts +30 -4
  263. package/dist/observability/secret-env-scrub.js +15 -3
  264. package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
  265. package/dist/orchestration/workflow-agent-session-index.js +1 -6
  266. package/dist/orchestration/workflow-completion-inbox.d.ts +26 -6
  267. package/dist/orchestration/workflow-completion-inbox.js +63 -45
  268. package/dist/orchestration/workflow-notify-journal.js +2 -3
  269. package/dist/org-delivery-gate.js +1 -1
  270. package/dist/org-memory-admission.d.ts +5 -2
  271. package/dist/org-memory-admission.js +25 -10
  272. package/dist/parked-decide.d.ts +33 -26
  273. package/dist/parked-decide.js +36 -44
  274. package/dist/peer-directory.d.ts +6 -12
  275. package/dist/peer-directory.js +5 -1
  276. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  277. package/dist/plugins/approval-ask-store-file.js +37 -4
  278. package/dist/plugins/approval-ask-store-memory.d.ts +5 -3
  279. package/dist/plugins/approval-ask-store-memory.js +11 -2
  280. package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
  281. package/dist/plugins/approval-ask-store-sql.js +3 -1
  282. package/dist/plugins/blob-backend.d.ts +38 -195
  283. package/dist/plugins/blob-backend.js +33 -229
  284. package/dist/plugins/caching-session-store.d.ts +12 -5
  285. package/dist/plugins/caching-session-store.js +3 -0
  286. package/dist/plugins/checkpoint-index-columns.d.ts +1 -1
  287. package/dist/plugins/checkpoint-index-columns.js +3 -2
  288. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  289. package/dist/plugins/checkpoint-store-face.js +152 -0
  290. package/dist/plugins/device-store-sql.d.ts +2 -2
  291. package/dist/plugins/device-store-sql.js +5 -10
  292. package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
  293. package/dist/plugins/execution-lane-rename-m40.js +10 -0
  294. package/dist/plugins/execution-placement-sql.d.ts +63 -88
  295. package/dist/plugins/execution-placement-sql.js +94 -54
  296. package/dist/plugins/file-run-store.d.ts +26 -18
  297. package/dist/plugins/file-run-store.js +64 -75
  298. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  299. package/dist/plugins/fork-routing-session-store.js +2 -2
  300. package/dist/plugins/imported-layer-root-retirement.d.ts +22 -11
  301. package/dist/plugins/imported-layer-root-retirement.js +4 -8
  302. package/dist/plugins/inbox-payload-column.d.ts +14 -0
  303. package/dist/plugins/inbox-payload-column.js +13 -0
  304. package/dist/plugins/local-checkpoint-store.d.ts +7 -2
  305. package/dist/plugins/local-checkpoint-store.js +13 -8
  306. package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
  307. package/dist/plugins/local-data-dir-modes-m39.js +182 -0
  308. package/dist/plugins/local-engine-high-water.d.ts +11 -0
  309. package/dist/plugins/local-engine-high-water.js +59 -0
  310. package/dist/plugins/local-session-store.d.ts +119 -30
  311. package/dist/plugins/local-session-store.js +156 -53
  312. package/dist/plugins/local-task-attachment-store.d.ts +2 -0
  313. package/dist/plugins/local-task-attachment-store.js +10 -11
  314. package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
  315. package/dist/plugins/memory-embedder-fingerprint.js +52 -12
  316. package/dist/plugins/memory-embedder.d.ts +1 -1
  317. package/dist/plugins/memory-embedder.js +2 -2
  318. package/dist/plugins/memory-engine-pg.d.ts +30 -6
  319. package/dist/plugins/memory-engine-pg.js +96 -214
  320. package/dist/plugins/memory-engine-sql-apply.d.ts +130 -0
  321. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  322. package/dist/plugins/memory-engine-tidb.d.ts +38 -9
  323. package/dist/plugins/memory-engine-tidb.js +99 -230
  324. package/dist/plugins/memory-key-guards.d.ts +1 -1
  325. package/dist/plugins/memory-origin-law.d.ts +3 -3
  326. package/dist/plugins/one-time-migrations.d.ts +169 -83
  327. package/dist/plugins/one-time-migrations.js +295 -235
  328. package/dist/plugins/owner-only-file-mode.d.ts +29 -0
  329. package/dist/plugins/owner-only-file-mode.js +141 -0
  330. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  331. package/dist/plugins/permission-rule-store-file.js +20 -1
  332. package/dist/plugins/pg-pool.d.ts +2 -1
  333. package/dist/plugins/pg-pool.js +12 -5
  334. package/dist/plugins/release-version.d.ts +20 -0
  335. package/dist/plugins/release-version.js +28 -0
  336. package/dist/plugins/remote-env-adb.d.ts +20 -4
  337. package/dist/plugins/remote-env-adb.js +39 -11
  338. package/dist/plugins/remote-env-e2b.d.ts +12 -3
  339. package/dist/plugins/remote-env-e2b.js +6 -4
  340. package/dist/plugins/remote-env-host.d.ts +9 -1
  341. package/dist/plugins/remote-env-host.js +6 -5
  342. package/dist/plugins/remote-env-k8s.d.ts +5 -2
  343. package/dist/plugins/remote-env-k8s.js +15 -4
  344. package/dist/plugins/remote-env-local-docker.d.ts +4 -1
  345. package/dist/plugins/remote-env-local-docker.js +3 -3
  346. package/dist/plugins/remote-env-ssh.d.ts +8 -2
  347. package/dist/plugins/remote-env-ssh.js +26 -24
  348. package/dist/plugins/remote-shell.d.ts +59 -30
  349. package/dist/plugins/remote-shell.js +175 -62
  350. package/dist/plugins/retention-declaration.d.ts +37 -0
  351. package/dist/plugins/retention-declaration.js +9 -0
  352. package/dist/plugins/retention-deployment.d.ts +126 -0
  353. package/dist/plugins/retention-deployment.js +88 -0
  354. package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
  355. package/dist/plugins/retention-lane-store-sql.js +103 -1
  356. package/dist/plugins/retention-sql.d.ts +29 -0
  357. package/dist/plugins/retention-sql.js +8 -0
  358. package/dist/plugins/run-store-sql.js +2 -2
  359. package/dist/plugins/schema-consistency.d.ts +0 -6
  360. package/dist/plugins/schema-consistency.js +1 -2
  361. package/dist/plugins/schema-ensure-legs.d.ts +1 -1
  362. package/dist/plugins/schema-ensure-legs.js +13 -3
  363. package/dist/plugins/session-ext-table.d.ts +74 -0
  364. package/dist/plugins/session-ext-table.js +121 -0
  365. package/dist/plugins/session-fork.d.ts +31 -9
  366. package/dist/plugins/session-fork.js +3 -2
  367. package/dist/plugins/session-placement.d.ts +4 -2
  368. package/dist/plugins/session-row-sql.d.ts +8 -7
  369. package/dist/plugins/session-row-sql.js +15 -7
  370. package/dist/plugins/session-store-sql.d.ts +131 -0
  371. package/dist/plugins/session-store-sql.js +298 -0
  372. package/dist/plugins/session-workspace-cell.d.ts +2 -2
  373. package/dist/plugins/session-workspace-cell.js +2 -2
  374. package/dist/plugins/shared-memory-store-sql.d.ts +10 -0
  375. package/dist/plugins/shared-memory-store-sql.js +13 -13
  376. package/dist/plugins/snapshot-bucket-presence.js +1 -1
  377. package/dist/plugins/sql-driver.d.ts +23 -7
  378. package/dist/plugins/sql-driver.js +41 -8
  379. package/dist/plugins/sql-errors.d.ts +5 -2
  380. package/dist/plugins/sql-errors.js +4 -0
  381. package/dist/plugins/sql-json-column.d.ts +5 -1
  382. package/dist/plugins/store-backend.d.ts +46 -16
  383. package/dist/plugins/store-backend.js +192 -69
  384. package/dist/plugins/store-contracts.d.ts +10 -13
  385. package/dist/plugins/store-contracts.js +1 -9
  386. package/dist/plugins/store-sweep.d.ts +63 -0
  387. package/dist/plugins/store-sweep.js +48 -0
  388. package/dist/plugins/task-attachment-store.d.ts +6 -8
  389. package/dist/plugins/task-attachment-store.js +15 -10
  390. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  391. package/dist/plugins/text-column-capacity.d.ts +21 -0
  392. package/dist/plugins/text-column-capacity.js +5 -0
  393. package/dist/plugins/tidb-pool.d.ts +3 -2
  394. package/dist/plugins/tidb-pool.js +20 -32
  395. package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
  396. package/dist/prompt-screen.d.ts +6 -9
  397. package/dist/prompt-screen.js +2 -5
  398. package/dist/read-face-posture.d.ts +6 -6
  399. package/dist/read-face-posture.js +2 -2
  400. package/dist/request-value-closure.d.ts +5 -5
  401. package/dist/request-value-closure.js +1 -1
  402. package/dist/rules-consent.d.ts +48 -27
  403. package/dist/rules-consent.js +147 -23
  404. package/dist/run-cancel-context.d.ts +4 -1
  405. package/dist/run-cancel-context.js +2 -1
  406. package/dist/run-local.js +17 -13
  407. package/dist/runs.d.ts +48 -30
  408. package/dist/runs.js +27 -17
  409. package/dist/runtime-caps-resolver.d.ts +15 -8
  410. package/dist/runtime-caps-resolver.js +14 -10
  411. package/dist/sandbox-egress-policy.js +6 -11
  412. package/dist/sandbox-rbac-rules.d.ts +88 -0
  413. package/dist/sandbox-rbac-rules.js +80 -0
  414. package/dist/sealed-key-layout.d.ts +16 -0
  415. package/dist/sealed-key-layout.js +3 -0
  416. package/dist/sealed-key.js +8 -6
  417. package/dist/security.d.ts +43 -9
  418. package/dist/security.js +9 -2
  419. package/dist/sema-config-dir.js +2 -2
  420. package/dist/server-secret-env.d.ts +2 -0
  421. package/dist/service-version.d.ts +2 -1
  422. package/dist/service-version.js +2 -2
  423. package/dist/session-error-wire.d.ts +1 -1
  424. package/dist/session-incarnation.d.ts +8 -5
  425. package/dist/session-notify-door.d.ts +5 -3
  426. package/dist/session-resident-background.d.ts +110 -0
  427. package/dist/session-resident-background.js +88 -0
  428. package/dist/session-row.d.ts +1 -1
  429. package/dist/session-sync-kernel.d.ts +53 -14
  430. package/dist/session-sync-kernel.js +70 -1
  431. package/dist/session-sync.d.ts +5 -3
  432. package/dist/session-workspace.d.ts +12 -2
  433. package/dist/session-workspace.js +6 -0
  434. package/dist/sighup-idle.d.ts +9 -7
  435. package/dist/sighup-idle.js +4 -5
  436. package/dist/spec-fields.d.ts +16 -0
  437. package/dist/spec-fields.js +7 -0
  438. package/dist/store-posture.d.ts +2 -1
  439. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  440. package/dist/stored-request-env-credentials-m38.js +8 -0
  441. package/dist/stored-request-hooks-m20.d.ts +3 -2
  442. package/dist/stored-request-hooks-m20.js +19 -14
  443. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  444. package/dist/stores-sql/blob-index-sql.d.ts +148 -0
  445. package/dist/stores-sql/blob-index-sql.js +154 -0
  446. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +14 -428
  447. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +43 -220
  448. package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
  449. package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
  450. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  451. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  452. package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +27 -67
  453. package/dist/{plugins → stores-sql}/pg-session-storage.js +82 -207
  454. package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +123 -217
  455. package/dist/{plugins → stores-sql}/retention-store-sql.js +66 -190
  456. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  457. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  458. package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
  459. package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
  460. package/dist/stores-sql/session-policy-store-sql.d.ts +1 -1
  461. package/dist/stores-sql/session-write-hooks.d.ts +59 -0
  462. package/dist/stores-sql/session-write-hooks.js +2 -0
  463. package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +10 -11
  464. package/dist/{plugins → stores-sql}/tidb-session-storage.js +10 -21
  465. package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +35 -112
  466. package/dist/{plugins → stores-sql}/tidb-session-store.js +73 -169
  467. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +3 -3
  468. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  469. package/dist/stores-sql/windowed-session-storage.d.ts +56 -0
  470. package/dist/stores-sql/windowed-session-storage.js +61 -0
  471. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  472. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  473. package/dist/task-a2a.d.ts +7 -1
  474. package/dist/task-a2a.js +15 -14
  475. package/dist/task-cwd.d.ts +11 -3
  476. package/dist/task-cwd.js +17 -1
  477. package/dist/task-mcp.d.ts +10 -2
  478. package/dist/task-mcp.js +13 -16
  479. package/dist/task-settings.d.ts +68 -8
  480. package/dist/task-settings.js +63 -6
  481. package/dist/tool-approval.d.ts +23 -10
  482. package/dist/tool-approval.js +10 -6
  483. package/dist/trace/core-keyset-guard.d.ts +17 -10
  484. package/dist/trace/engine-notice-wire.d.ts +1 -1
  485. package/dist/trace/engine-notice-wire.js +4 -0
  486. package/dist/trace/exit-seat.d.ts +29 -1
  487. package/dist/trace/exit-seat.js +4 -0
  488. package/dist/trace/project.d.ts +33 -16
  489. package/dist/trace/project.js +21 -9
  490. package/dist/trace/redact.d.ts +13 -2
  491. package/dist/trace/redact.js +347 -78
  492. package/dist/trace/self-minted-wire.d.ts +587 -16
  493. package/dist/trace/self-minted-wire.js +215 -9
  494. package/dist/trace/sema-provenance.d.ts +45 -41
  495. package/dist/trace/sema-provenance.js +81 -10
  496. package/dist/trace/task-notification-facets.d.ts +70 -2
  497. package/dist/trace/task-notification-facets.js +71 -1
  498. package/dist/trace/wire-projection-faces.d.ts +39 -3
  499. package/dist/trace/wire-projection-faces.js +27 -2
  500. package/dist/unread-request-members.d.ts +66 -0
  501. package/dist/unread-request-members.js +41 -0
  502. package/dist/usage-analytics.d.ts +4 -0
  503. package/dist/user-input-ledger.d.ts +2 -2
  504. package/dist/web-admin-client/apply-effective.js +22 -26
  505. package/dist/web-admin-client/candidate-gate.d.ts +35 -3
  506. package/dist/web-admin-client/candidate-gate.js +53 -5
  507. package/dist/web-admin-client/center-request.d.ts +4 -1
  508. package/dist/web-admin-client/center-request.js +2 -1
  509. package/dist/web-admin-client/effective-keys.js +0 -1
  510. package/dist/web-admin-client/facade.d.ts +1 -1
  511. package/dist/web-admin-client/http-client.d.ts +1 -0
  512. package/dist/web-admin-client/http-client.js +17 -9
  513. package/dist/web-admin-client/restart-signal.js +5 -2
  514. package/dist/web-admin-client/skills-mcp.d.ts +20 -6
  515. package/dist/web-admin-client/skills-mcp.js +26 -21
  516. package/dist/web-admin-client/types.d.ts +34 -49
  517. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +2 -2
  518. package/package.json +22 -13
  519. package/scripts/drain-check-local.mjs +8 -55
  520. package/scripts/drain-check.mysql.sql +3 -40
  521. package/scripts/drain-check.pg.sql +3 -40
  522. package/skills/update-config.md +1 -1
  523. package/wire-changes/server@7.146.0.json +3221 -0
  524. package/wire-changes/server@7.147.0.json +813 -0
  525. package/dist/approval-batch-census.d.ts +0 -27
  526. package/dist/approval-batch-census.js +0 -27
@@ -7,7 +7,7 @@ import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/s
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 } from "./classic-stations.js";
10
+ import { hangClassic, wrapClassic } 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
13
  import { LIMIT_REASONS } from "../limit-refusal.js";
@@ -19,8 +19,8 @@ import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTP
19
19
  import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
20
20
  import { renderBranchTranscript } from "./branch-transcript.js";
21
21
  import { buildRequestUrlCredentialRefusal, urlCredentialHeaderExit } from "../url-credential-refusal.js";
22
- import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
23
- import { commandLineChars, composeHookEnvLayer, hookPluginChars, planCommandSpawn, readHookArgs, readHookPlugin } from "./command-spawn.js";
22
+ import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookLaneOf, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
23
+ import { commandLineChars, composeHookEnvLayer, hookPluginChars, planCommandSpawn, readHookPlugin } from "./command-spawn.js";
24
24
  export const MAX_HOOK_ENTRIES_PER_EVENT = 32;
25
25
  export const MAX_HOOK_COMMAND_CHARS = 8_192;
26
26
  export const MAX_HOOK_TIMEOUT_SECONDS = 600;
@@ -52,14 +52,27 @@ export const SETTINGS_HOOK_STATIONS = {
52
52
  PreCompact: ["classic.PreCompact"],
53
53
  PostCompact: ["classic.PostCompact"],
54
54
  };
55
- function hangTranslator(on, event, translators) {
55
+ function chainsBelowTaskOn(patterns, station) {
56
+ return patterns.some((p) => p === station || (p.endsWith(".*") && station.startsWith(p.slice(0, -1))));
57
+ }
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) {
56
64
  const translate = translators[event];
57
65
  if (translate === undefined)
58
66
  return;
67
+ const hang = lane === "managed" ? wrapClassic : hangClassic;
59
68
  for (const station of SETTINGS_HOOK_STATIONS[event])
60
- hangClassic(on, station, translate);
69
+ hang(on, station, translate);
61
70
  }
62
- export const SETTINGS_HOOKS_REGISTRATION = "settings.hooks";
71
+ export const SETTINGS_HOOKS_REGISTRATIONS = {
72
+ managed: "settings.hooks.managed",
73
+ unmanaged: "settings.hooks",
74
+ };
75
+ const LANE_CHAIN_ORDER = ["managed", "unmanaged"];
63
76
  export const DEFAULT_SETTINGS_HOOKS_BUDGET_MS = MAX_HOOK_TIMEOUT_SECONDS * 1000;
64
77
  export function settingsHooksBudgetMs(config) {
65
78
  return config.hooksTimeoutMs ?? DEFAULT_SETTINGS_HOOKS_BUDGET_MS;
@@ -222,7 +235,6 @@ const HOOKS_WIRE_KEY_SHAPE = {
222
235
  source: `source must be one of ${HOOK_SOURCE_VOCABULARY_SENTENCE}`,
223
236
  plugin: "plugin must be { name, root, dataDir?, options? } — name a non-empty string, root and dataDir absolute paths with no \"..\" segment and no NUL byte, " +
224
237
  "options an object of string values whose keys are identifiers ([A-Za-z_][A-Za-z0-9_]*); nothing else",
225
- args: "args must be an array of strings without NUL bytes (exec form: command is the executable, args its arguments)",
226
238
  };
227
239
  function hooksCapIssue(limit, event, path, count) {
228
240
  const c = HOOKS_CAPS[limit];
@@ -419,16 +431,12 @@ export function parseHooksConfig(raw) {
419
431
  return hooksRefused({ reason: "unsupported_entry_type", path: [...at, "type"], type: contractEntry.type });
420
432
  let entry = contractEntry;
421
433
  if (entry.type === "command") {
422
- const rawEntry = rawMember(rawMember(rawGroup, "hooks"), entryIndex);
423
- const rawArgs = rawMember(rawEntry, "args");
424
- const rawPlugin = rawMember(rawEntry, "plugin");
425
- const args = rawArgs === undefined ? undefined : readHookArgs(rawArgs);
426
- if (rawArgs !== undefined && args === undefined)
427
- return hooksRefused({ reason: "wire_key_invalid", path: [...at, "args"], key: "args" });
434
+ const rawPlugin = rawMember(rawMember(rawMember(rawGroup, "hooks"), entryIndex), "plugin");
428
435
  const plugin = rawPlugin === undefined ? undefined : readHookPlugin(rawPlugin);
429
436
  if (rawPlugin !== undefined && plugin === undefined)
430
437
  return hooksRefused({ reason: "wire_key_invalid", path: [...at, "plugin"], key: "plugin" });
431
- entry = { ...entry, ...(args !== undefined ? { args } : {}), ...(plugin !== undefined ? { plugin } : {}) };
438
+ if (plugin !== undefined)
439
+ entry = { ...entry, plugin };
432
440
  const line = commandLineChars(entry);
433
441
  if (line > MAX_HOOK_COMMAND_CHARS)
434
442
  return hooksRefused(hooksCapIssue("command", event, [...at, "command"], line));
@@ -587,7 +595,7 @@ function noteMatcherOverBudget(ctx, args) {
587
595
  detail: `${what} \`${expression}\` did not finish within ${budgetMs} ms on \`${value}\`; treated as selected (${count} ${count === 1 ? "entry" : "entries"})`,
588
596
  });
589
597
  }
590
- function matcherGroupSelected(ctx, event, group, value, toolName) {
598
+ function matcherGroupSelected(ctx, event, group, value, toolName, entries) {
591
599
  const answer = hookMatcherMatches(group.matcher, value);
592
600
  switch (answer) {
593
601
  case true:
@@ -598,7 +606,7 @@ function matcherGroupSelected(ctx, event, group, value, toolName) {
598
606
  ctx.logger.warn("hook_matcher_invalid", { event, matcher: echoCallerText(group.matcher ?? "", { maxChars: MATCHER_ECHO_CHARS }) });
599
607
  return "invalid";
600
608
  case "over_budget":
601
- noteMatcherOverBudget(ctx, { event, scope: "matcher", expression: group.matcher ?? "", value, entries: group.hooks, ...(toolName !== undefined ? { toolName } : {}) });
609
+ noteMatcherOverBudget(ctx, { event, scope: "matcher", expression: group.matcher ?? "", value, entries, ...(toolName !== undefined ? { toolName } : {}) });
602
610
  return "over_budget";
603
611
  default: {
604
612
  const unreachable = answer;
@@ -641,6 +649,41 @@ export const PERMISSIVE_VERDICT_EVENTS = {
641
649
  PreModelSwitch: false,
642
650
  PostModelSwitch: false,
643
651
  };
652
+ export const ON_FAILURE_BLOCK_EVENTS = {
653
+ PreToolUse: true,
654
+ PostToolUse: true,
655
+ PostToolUseFailure: true,
656
+ PostToolBatch: true,
657
+ Notification: true,
658
+ UserPromptSubmit: true,
659
+ UserPromptExpansion: true,
660
+ SessionStart: true,
661
+ SessionEnd: true,
662
+ Stop: false,
663
+ StopFailure: false,
664
+ SubagentStart: true,
665
+ SubagentStop: false,
666
+ PreCompact: true,
667
+ PostCompact: false,
668
+ PermissionRequest: true,
669
+ PermissionDenied: true,
670
+ Setup: true,
671
+ TeammateIdle: false,
672
+ TaskCreated: true,
673
+ TaskCompleted: false,
674
+ Elicitation: true,
675
+ ElicitationResult: true,
676
+ ConfigChange: true,
677
+ WorktreeCreate: true,
678
+ WorktreeRemove: true,
679
+ InstructionsLoaded: true,
680
+ CwdChanged: true,
681
+ FileChanged: true,
682
+ MessageDisplay: true,
683
+ DirectoryAdded: true,
684
+ PreModelSwitch: true,
685
+ PostModelSwitch: true,
686
+ };
644
687
  const DISTRUST_DETAIL = {
645
688
  input_truncated: `the hook did not receive the whole payload (tool_input over ${MAX_TOOL_INPUT_CHARS} characters was replaced by a truncation marker, or a prompt / agent entry's payload text was clipped at that size); its allow / updatedInput are not honored`,
646
689
  matcher_over_budget: "the matcher (or the if condition's tool-name part) did not finish within its budget; the entry ran as selected, so its allow / updatedInput are not honored",
@@ -650,7 +693,7 @@ function noteVerdictUntrusted(ctx, args) {
650
693
  kind: "hook_verdict_untrusted",
651
694
  event: args.event,
652
695
  reason: args.reason,
653
- lane: hookPhaseOf(args.source) === "unmanaged" ? "unmanaged" : "managed",
696
+ lane: hookLaneOf(args.source),
654
697
  hookName: boundedNoticeText(hookDisplayName(args.entry)),
655
698
  entryType: args.entry.type,
656
699
  ...(args.toolName !== undefined ? { toolName: args.toolName } : {}),
@@ -692,15 +735,22 @@ const VERDICT_GAP_CAUSE = {
692
735
  };
693
736
  function createUnrunLedger(ctx, event, toolName) {
694
737
  let managedVerdictGap;
695
- return {
738
+ let haltNoted = false;
739
+ const ledger = {
740
+ halted(source, entry) {
741
+ if (haltNoted)
742
+ return;
743
+ haltNoted = true;
744
+ ledger.unrun(source, entry, "budget_exhausted", ENGINE_BUDGET_SPENT_DETAIL);
745
+ },
696
746
  untrusted(source, entry, reason) {
697
- if (hookPhaseOf(source) === "unmanaged")
747
+ if (hookLaneOf(source) === "unmanaged")
698
748
  return;
699
749
  managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
700
750
  noteVerdictUntrusted(ctx, { event, reason, entry, source, toolName });
701
751
  },
702
752
  unrun(source, entry, reason, detail) {
703
- if (source === undefined || hookPhaseOf(source) === "unmanaged")
753
+ if (source === undefined || hookLaneOf(source) === "unmanaged")
704
754
  return;
705
755
  managedVerdictGap ??= VERDICT_GAP_CAUSE[reason];
706
756
  ctx.logger.warn("hook_managed_unrun", { event, reason, source: echoCallerText(source), type: entry.type, ...(toolName !== undefined ? { tool: toolName } : {}) });
@@ -721,6 +771,7 @@ function createUnrunLedger(ctx, event, toolName) {
721
771
  return managedVerdictGap;
722
772
  },
723
773
  };
774
+ return ledger;
724
775
  }
725
776
  function noteExitNonzero(ctx, ledger, event, ran, toolName, detail) {
726
777
  logHookCommandFailed(ctx, event, ran.run);
@@ -766,6 +817,39 @@ function buildHookContextField(parts) {
766
817
  const text = buildHookContext(parts);
767
818
  return text === undefined ? {} : { additionalContext: text };
768
819
  }
820
+ function belowString(below, key) {
821
+ if (typeof below !== "object" || below === null || !Object.hasOwn(below, key))
822
+ return undefined;
823
+ const value = Reflect.get(below, key);
824
+ return typeof value === "string" ? value : undefined;
825
+ }
826
+ function belowMembers(below) {
827
+ return typeof below === "object" && below !== null ? { ...below } : {};
828
+ }
829
+ function withBelowContext(parts, below) {
830
+ const tail = belowString(below, "additionalContext");
831
+ return tail === undefined ? [...parts] : [...parts, tail];
832
+ }
833
+ function alsoAsks(below, own) {
834
+ return below === undefined || below === own ? own : `${below} (a managed PreToolUse hook also asks: ${own})`;
835
+ }
836
+ function readBelowPreToolUse(below) {
837
+ const action = belowString(below, "action");
838
+ const message = belowString(below, "message") ?? "a PreToolUse hook requires approval";
839
+ const cause = belowString(below, "questionCause");
840
+ const rewrite = typeof below === "object" && below !== null && Object.hasOwn(below, "updatedInput") ? Reflect.get(below, "updatedInput") : undefined;
841
+ const additionalContext = belowString(below, "additionalContext");
842
+ const unreadableCause = cause === "verdict_unfinished" ? "verdict_unfinished" : "verdict_unreadable";
843
+ return {
844
+ denied: action === "deny",
845
+ allowed: action === "allow",
846
+ ...(action === "ask" && (cause === undefined || cause === "asked") ? { asked: message } : {}),
847
+ ...(action === "ask" && cause !== undefined && cause !== "asked" ? { unreadable: { message, cause: unreadableCause } } : {}),
848
+ realApproval: action === "ask" && typeof below === "object" && below !== null && Reflect.get(below, "requiresRealApproval") === true,
849
+ ...(rewrite !== null && typeof rewrite === "object" ? { updatedInput: rewrite } : {}),
850
+ ...(additionalContext !== undefined ? { additionalContext } : {}),
851
+ };
852
+ }
769
853
  function spawnPlannedCommand(plan, ctx, stdio) {
770
854
  const env = hookChildEnv(plan.env, true);
771
855
  if (plan.kind === "exec")
@@ -1139,18 +1223,39 @@ function boundedToolInputPayload(payload) {
1139
1223
  return bounded === p.tool_input ? payload : { ...p, tool_input: bounded };
1140
1224
  }
1141
1225
  function readHookStdout(ran, event, ctx, payload, ledger) {
1142
- const { run, entry } = ran;
1143
- const out = parseHookStdout(run.stdout);
1226
+ const out = parseHookStdout(ran.run.stdout);
1144
1227
  if (out !== undefined)
1145
1228
  return out;
1146
- const t = run.stdout.trim();
1147
- if (t.startsWith("{") || t.startsWith("[")) {
1148
- ctx.logger.warn("hook_bad_json_output", { event, type: entry.type, preview: seatText("operator-log-text", t) });
1149
- noteHookFailure(ctx, { event, entry, reason: "bad_json", exitCode: run.code, stderr: run.stderr, toolName: toolNameOf(payload) });
1150
- ledger.unrun(ran.source, entry, "bad_json");
1151
- }
1229
+ if (stdoutVerdictUnreadable(ran.run.stdout))
1230
+ noteBadJson(ctx, ledger, event, ran, toolNameOf(payload));
1152
1231
  return undefined;
1153
1232
  }
1233
+ function stdoutVerdictUnreadable(stdout) {
1234
+ if (parseHookStdout(stdout) !== undefined)
1235
+ return false;
1236
+ const t = stdout.trim();
1237
+ return t.startsWith("{") || t.startsWith("[");
1238
+ }
1239
+ function noteBadJson(ctx, ledger, event, ran, toolName) {
1240
+ const { run, entry } = ran;
1241
+ ctx.logger.warn("hook_bad_json_output", { event, type: entry.type, preview: seatText("operator-log-text", run.stdout.trim()) });
1242
+ noteHookFailure(ctx, { event, entry, reason: "bad_json", exitCode: run.code, stderr: run.stderr, toolName });
1243
+ ledger.unrun(ran.source, entry, "bad_json");
1244
+ }
1245
+ const PERMISSION_DECISION_WORDS = new Set(["allow", "deny", "ask"]);
1246
+ function preToolUseDecisionOf(out) {
1247
+ const hso = out?.hookSpecificOutput;
1248
+ return hso !== null && typeof hso === "object" && hso.hookEventName === "PreToolUse" ? hso.permissionDecision : undefined;
1249
+ }
1250
+ function noteDecisionOutOfSet(ctx, ledger, ran, decision, toolName, effect) {
1251
+ ctx.logger.warn("hook_permission_decision_unknown", {
1252
+ event: "PreToolUse",
1253
+ decision: seatText("operator-log-text", typeof decision === "string" ? decision : typeof decision),
1254
+ tool: toolName,
1255
+ effect,
1256
+ });
1257
+ ledger.unrun(ran.source, ran.entry, "decision_out_of_set");
1258
+ }
1154
1259
  function parseHookStdout(stdout) {
1155
1260
  const t = stdout.trim();
1156
1261
  if (!t.startsWith("{"))
@@ -1183,14 +1288,70 @@ function runRunnableEntry(entry, payload, ctx, event, llmExtra, halt) {
1183
1288
  function isSyncEntry(entry) {
1184
1289
  return !(entry.type === "command" && (entry.async === true || entry.asyncRewake === true));
1185
1290
  }
1291
+ function blocksOnFailure(entry, event) {
1292
+ if (!ON_FAILURE_BLOCK_EVENTS[event] || !isSyncEntry(entry))
1293
+ return false;
1294
+ if (entry.type !== "command" && entry.type !== "http")
1295
+ return false;
1296
+ const word = entry.onFailure;
1297
+ switch (word) {
1298
+ case "block":
1299
+ return true;
1300
+ case "continue":
1301
+ case undefined:
1302
+ return false;
1303
+ default: {
1304
+ const unreachable = word;
1305
+ void unreachable;
1306
+ return true;
1307
+ }
1308
+ }
1309
+ }
1310
+ function blockingFailureRun(event, cause) {
1311
+ return { code: 2, stdout: "", stderr: `${event} hook did not reach a verdict (${cause}); blocking because onFailure is "block"`, timedOut: false };
1312
+ }
1313
+ function ranVerdictFlaw(event, run) {
1314
+ if (run.code === 2)
1315
+ return undefined;
1316
+ if (run.code !== 0)
1317
+ return { reason: "exit_nonzero" };
1318
+ if (stdoutVerdictUnreadable(run.stdout))
1319
+ return { reason: "bad_json" };
1320
+ if (event !== "PreToolUse")
1321
+ return undefined;
1322
+ const out = parseHookStdout(run.stdout);
1323
+ if (out?.continue === false || out?.decision === "block")
1324
+ return undefined;
1325
+ const decision = preToolUseDecisionOf(out);
1326
+ return decision !== undefined && !PERMISSION_DECISION_WORDS.has(decision) ? { reason: "decision_out_of_set", decision } : undefined;
1327
+ }
1328
+ function noteRanVerdictFlaw(ctx, ledger, event, ran, flaw, toolName) {
1329
+ switch (flaw.reason) {
1330
+ case "exit_nonzero":
1331
+ noteExitNonzero(ctx, ledger, event, ran, toolName);
1332
+ return;
1333
+ case "bad_json":
1334
+ noteBadJson(ctx, ledger, event, ran, toolName);
1335
+ return;
1336
+ case "decision_out_of_set":
1337
+ noteDecisionOutOfSet(ctx, ledger, ran, flaw.decision, toolName, 'hook ruling not honored (closed word set is allow|deny|ask); the entry\'s onFailure is "block", so this call is denied');
1338
+ return;
1339
+ default: {
1340
+ const unreachable = flaw;
1341
+ void unreachable;
1342
+ }
1343
+ }
1344
+ }
1186
1345
  async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceFired, halt, deadlineAt, ledger, llmExtra) {
1187
1346
  const results = [];
1188
1347
  let llmEntries = 0;
1189
1348
  const toolName = toolNameOf(payload);
1190
1349
  const inputTruncated = boundedToolInputPayload(payload) !== payload;
1191
1350
  const truncatedFor = (entry) => (entry.type === "prompt" || entry.type === "agent" ? llmHookArguments(payload).truncated : inputTruncated);
1351
+ const blockedBy = (entry, source, cause) => ({ entry, run: blockingFailureRun(event, cause), source });
1352
+ let laneSpent = false;
1192
1353
  const settle = async (entry, onceKey) => {
1193
- if (halt.aborted)
1354
+ if (halt.signal.aborted)
1194
1355
  return { kind: "halted" };
1195
1356
  if (entry.once && onceFired.has(onceKey))
1196
1357
  return { kind: "passed" };
@@ -1234,23 +1395,26 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1234
1395
  if (entry.type === "command" && (entry.async || entry.asyncRewake)) {
1235
1396
  if (entry.once)
1236
1397
  onceFired.add(onceKey);
1237
- void fireAsyncCommandHook(entry, payload, ctx, event, halt);
1398
+ void fireAsyncCommandHook(entry, payload, ctx, event, halt.signal);
1238
1399
  return { kind: "passed" };
1239
1400
  }
1240
- if (Date.now() > deadlineAt) {
1241
- ctx.logger.warn("hook_event_deadline_exceeded", { event, budgetSec: MAX_HOOK_EVENT_TOTAL_SECONDS });
1401
+ if (laneSpent || Date.now() > deadlineAt) {
1402
+ if (!laneSpent)
1403
+ ctx.logger.warn("hook_event_deadline_exceeded", { event, budgetSec: MAX_HOOK_EVENT_TOTAL_SECONDS });
1242
1404
  return { kind: "unrun", reason: "budget_exhausted" };
1243
1405
  }
1244
1406
  if (entry.once)
1245
1407
  onceFired.add(onceKey);
1246
1408
  if (entry.type === "prompt" || entry.type === "agent")
1247
1409
  llmEntries++;
1248
- const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra, halt);
1410
+ const run = await runRunnableEntry(entry, payload, ctx, event, llmExtra, halt.signal);
1249
1411
  if (run.aborted !== undefined) {
1250
1412
  if (run.aborted.signal !== undefined)
1251
1413
  ctx.logger.warn("hook_command_killed", { event, type: entry.type, killed: true, signal: run.aborted.signal });
1252
1414
  return { kind: "halted" };
1253
1415
  }
1416
+ if (halt.signal.aborted)
1417
+ return { kind: "halted" };
1254
1418
  if (run.withheld !== undefined) {
1255
1419
  const { list, rule, sources } = run.withheld;
1256
1420
  ctx.logger.warn("hook_http_withheld", { event, url: entry.type === "http" ? buildDisplaySafeUrl(entry.url) : null, list, rule, sources: [...sources] });
@@ -1274,19 +1438,35 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1274
1438
  return ifOverBudget ? { kind: "ran", run, overBudget: true } : { kind: "ran", run };
1275
1439
  };
1276
1440
  for (const { group, index } of groups) {
1277
- const selected = matcherGroupSelected(ctx, event, group, matchValue, toolName);
1441
+ const pending = laneSpent ? group.hooks.filter((entry) => blocksOnFailure(entry, event)) : group.hooks;
1442
+ if (laneSpent && pending.length === 0)
1443
+ continue;
1444
+ const selected = matcherGroupSelected(ctx, event, group, matchValue, toolName, pending);
1278
1445
  if (selected === "invalid") {
1279
- for (const entry of group.hooks)
1280
- if (isSyncEntry(entry))
1446
+ for (const entry of pending) {
1447
+ if (!isSyncEntry(entry))
1448
+ continue;
1449
+ if (!laneSpent)
1281
1450
  ledger.unrun(group.source, entry, "matcher_invalid");
1451
+ if (blocksOnFailure(entry, event))
1452
+ results.push(blockedBy(entry, group.source, "matcher_invalid"));
1453
+ }
1282
1454
  continue;
1283
1455
  }
1284
1456
  if (selected === false)
1285
1457
  continue;
1286
1458
  for (const [ei, entry] of group.hooks.entries()) {
1459
+ if (laneSpent && !blocksOnFailure(entry, event))
1460
+ continue;
1287
1461
  const fate = await settle(entry, `${event}:${index}:${ei}`);
1288
1462
  switch (fate.kind) {
1289
1463
  case "ran": {
1464
+ const flaw = blocksOnFailure(entry, event) ? ranVerdictFlaw(event, fate.run) : undefined;
1465
+ if (flaw !== undefined) {
1466
+ noteRanVerdictFlaw(ctx, ledger, event, { entry, run: fate.run, source: group.source }, flaw, toolName);
1467
+ results.push(blockedBy(entry, group.source, flaw.reason));
1468
+ break;
1469
+ }
1290
1470
  const distrust = !PERMISSIVE_VERDICT_EVENTS[event]
1291
1471
  ? undefined
1292
1472
  : truncatedFor(entry)
@@ -1300,13 +1480,18 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1300
1480
  break;
1301
1481
  }
1302
1482
  case "unrun":
1303
- ledger.unrun(group.source, entry, fate.reason);
1483
+ if (!laneSpent)
1484
+ ledger.unrun(group.source, entry, fate.reason);
1485
+ if (blocksOnFailure(entry, event))
1486
+ results.push(blockedBy(entry, group.source, fate.reason));
1304
1487
  if (fate.reason === "budget_exhausted")
1305
- return results;
1488
+ laneSpent = true;
1306
1489
  break;
1307
1490
  case "passed":
1308
1491
  break;
1309
1492
  case "halted":
1493
+ if (isSyncEntry(entry) && halt.budgetSpent())
1494
+ ledger.halted(group.source, entry);
1310
1495
  return results;
1311
1496
  default: {
1312
1497
  const unreachable = fate;
@@ -1317,14 +1502,12 @@ async function runMatchingEntries(event, groups, matchValue, payload, ctx, onceF
1317
1502
  }
1318
1503
  return results;
1319
1504
  }
1320
- function createLaneClocks() {
1321
- const startedAt = {};
1322
- return (phase) => {
1323
- const lane = phase === "unmanaged" ? "unmanaged" : "managed";
1324
- return (startedAt[lane] ??= Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
1325
- };
1505
+ function createEventClock() {
1506
+ let deadlineAt;
1507
+ return () => (deadlineAt ??= Date.now() + MAX_HOOK_EVENT_TOTAL_SECONDS * 1000);
1326
1508
  }
1327
- function hookLanesOf(groups) {
1509
+ 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
+ function hookPhasesOf(groups) {
1328
1511
  const indexed = groups.map((group, index) => ({ group, index, phase: hookPhaseOf(group.source) }));
1329
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);
1330
1513
  }
@@ -1332,12 +1515,10 @@ function trustRank(phase) {
1332
1515
  return HOOK_PHASES.length - 1 - HOOK_PHASES.indexOf(phase);
1333
1516
  }
1334
1517
  async function runEventLanes(event, groups, matchValue, payload, ctx, onceFired, halt, ledger, llmExtra) {
1335
- const deadlineFor = createLaneClocks();
1518
+ const deadline = createEventClock();
1336
1519
  const ran = [];
1337
- for (const lane of hookLanesOf(groups)) {
1338
- if (halt.aborted)
1339
- break;
1340
- for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, onceFired, halt, deadlineFor(lane.phase), ledger, llmExtra))
1520
+ for (const lane of hookPhasesOf(groups)) {
1521
+ for (const r of await runMatchingEntries(event, lane.groups, matchValue, payload, ctx, onceFired, halt, deadline(), ledger, llmExtra))
1341
1522
  ran.push({ ...r, phase: lane.phase });
1342
1523
  }
1343
1524
  return ran.sort((a, b) => trustRank(a.phase) - trustRank(b.phase)).map(({ entry, run, source }) => ({ entry, run, source }));
@@ -1372,12 +1553,26 @@ async function runObserveOnlyEvent(event, groups, matchValue, payload, ctx, once
1372
1553
  }
1373
1554
  }
1374
1555
  }
1375
- export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1376
- const configured = Object.keys(config).filter((e) => (config[e]?.length ?? 0) > 0);
1377
- const shellOwned = configured.filter((e) => HOOK_EVENT_OWNER[e] === "shell");
1556
+ export function createSettingsHooksRegistrations(config, ctx, budgetMs) {
1557
+ const shellOwned = Object.keys(config).filter((e) => (config[e]?.length ?? 0) > 0 && HOOK_EVENT_OWNER[e] === "shell");
1378
1558
  if (shellOwned.length > 0) {
1379
1559
  ctx.logger.debug("hook_events_shell_owned", { events: shellOwned });
1380
1560
  }
1561
+ return LANE_CHAIN_ORDER.flatMap((lane) => {
1562
+ const registration = createLaneRegistration(lane, laneConfigOf(config, lane), ctx, budgetMs);
1563
+ return registration === undefined ? [] : [registration];
1564
+ });
1565
+ }
1566
+ function laneConfigOf(config, lane) {
1567
+ const out = {};
1568
+ for (const event of Object.keys(config)) {
1569
+ const groups = config[event]?.filter((group) => hookLaneOf(group.source) === lane);
1570
+ if (groups !== undefined && groups.length > 0)
1571
+ out[event] = groups;
1572
+ }
1573
+ return out;
1574
+ }
1575
+ function createLaneRegistration(lane, config, ctx, budgetMs) {
1381
1576
  const lit = (e) => {
1382
1577
  const groups = config[e];
1383
1578
  return groups && groups.length > 0 ? groups : undefined;
@@ -1397,17 +1592,23 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1397
1592
  const onceFired = new Set();
1398
1593
  const translators = {};
1399
1594
  if (pre) {
1400
- translators.PreToolUse = async (e, signal) => {
1595
+ const tightenOnly = managedTightenOnlyOn(lane, "classic.PreToolUse", ctx.belowTaskChainPatterns);
1596
+ translators.PreToolUse = async (e, halt, below) => {
1597
+ const prior = readBelowPreToolUse(below);
1598
+ const realApproval = prior.realApproval ? { requiresRealApproval: true } : {};
1599
+ if (prior.denied)
1600
+ return undefined;
1401
1601
  const toolName = e.tool;
1402
1602
  const ledger = createUnrunLedger(ctx, "PreToolUse", toolName);
1403
- const deadlineFor = createLaneClocks();
1404
- let currentInput = e.input;
1405
- let rewritten = false;
1406
- let asked;
1407
- let allowedFor;
1603
+ const deadline = createEventClock();
1604
+ let currentInput = prior.updatedInput ?? e.input;
1605
+ let rewritten = prior.updatedInput !== undefined;
1606
+ let asked = prior.asked;
1607
+ let spoke = false;
1608
+ let allowedFor = prior.allowed ? { input: currentInput } : undefined;
1408
1609
  let unknownDecision = false;
1409
1610
  const contexts = { unmanaged: [], managedPlugin: [], managedPolicy: [] };
1410
- const contextField = () => buildHookContextField([...HOOK_PHASES].sort((a, b) => trustRank(a) - trustRank(b)).flatMap((p) => contexts[p]));
1611
+ const contextField = () => buildHookContextField([...[...HOOK_PHASES].sort((a, b) => trustRank(a) - trustRank(b)).flatMap((p) => contexts[p]), ...(prior.additionalContext !== undefined ? [prior.additionalContext] : [])]);
1411
1612
  const runPass = async (phase, groups, once) => {
1412
1613
  let rewroteBy;
1413
1614
  for (const laneGroup of groups) {
@@ -1419,7 +1620,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1419
1620
  tool_input: groupInput,
1420
1621
  tool_use_id: e.tool_use_id,
1421
1622
  };
1422
- const singles = await runMatchingEntries("PreToolUse", [laneGroup], toolName, payload, ctx, once, signal, deadlineFor(phase), ledger);
1623
+ const singles = await runMatchingEntries("PreToolUse", [laneGroup], toolName, payload, ctx, once, halt, deadline(), ledger);
1423
1624
  for (const ran of singles) {
1424
1625
  const { entry, run } = ran;
1425
1626
  if (run.code === 2) {
@@ -1446,20 +1647,26 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1446
1647
  const hso = out.hookSpecificOutput;
1447
1648
  if (hso !== null && typeof hso === "object" && hso.hookEventName === "PreToolUse") {
1448
1649
  const h = hso;
1449
- if (typeof h.additionalContext === "string")
1650
+ if (typeof h.additionalContext === "string") {
1450
1651
  contexts[phase].push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
1451
- const decision = h.permissionDecision;
1652
+ spoke = true;
1653
+ }
1654
+ const decision = preToolUseDecisionOf(out);
1452
1655
  if (decision === "deny") {
1453
1656
  const reason = clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "denied by a PreToolUse hook", MAX_HOOK_FEEDBACK_CHARS);
1454
1657
  return { deny: { action: "deny", message: reason, ...contextField() } };
1455
1658
  }
1659
+ const proposed = h.updatedInput !== null && typeof h.updatedInput === "object" ? h.updatedInput : undefined;
1660
+ const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
1661
+ 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() } };
1663
+ }
1456
1664
  if (decision === "ask") {
1457
- asked = clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS);
1665
+ asked = alsoAsks(prior.asked ?? prior.unreadable?.message, clipDataText(typeof h.permissionDecisionReason === "string" ? h.permissionDecisionReason : "a PreToolUse hook requires approval", MAX_HOOK_FEEDBACK_CHARS));
1666
+ spoke = true;
1458
1667
  }
1459
1668
  if (decision === "allow" || decision === undefined) {
1460
- const proposed = h.updatedInput !== null && typeof h.updatedInput === "object" ? h.updatedInput : undefined;
1461
- const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
1462
- if (ran.distrust !== undefined && hookPhaseOf(ran.source) === "unmanaged" && (decision === "allow" || proposesRewrite)) {
1669
+ if (ran.distrust !== undefined && hookLaneOf(ran.source) === "unmanaged" && (decision === "allow" || proposesRewrite)) {
1463
1670
  noteVerdictUntrusted(ctx, { event: "PreToolUse", reason: ran.distrust, entry, source: ran.source, toolName });
1464
1671
  }
1465
1672
  const offered = ran.distrust === undefined ? proposed : undefined;
@@ -1468,26 +1675,24 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1468
1675
  currentInput = offered;
1469
1676
  rewritten = true;
1470
1677
  rewroteBy = ran;
1678
+ spoke = true;
1471
1679
  }
1472
- if (decision === "allow" && ran.distrust === undefined)
1680
+ if (decision === "allow" && ran.distrust === undefined) {
1473
1681
  allowedFor = { input: rewrites ? offered : groupInput };
1682
+ spoke = true;
1683
+ }
1474
1684
  }
1475
- else if (decision !== "deny" && decision !== "ask") {
1685
+ else if (!PERMISSION_DECISION_WORDS.has(decision)) {
1476
1686
  unknownDecision = true;
1477
- ctx.logger.warn("hook_permission_decision_unknown", {
1478
- event: "PreToolUse",
1479
- decision: seatText("operator-log-text", typeof decision === "string" ? decision : typeof decision),
1480
- tool: toolName,
1481
- effect: "hook ruling not honored (closed word set is allow|deny|ask); this call falls back to the engine gate",
1482
- });
1483
- ledger.unrun(ran.source, entry, "decision_out_of_set");
1687
+ spoke = true;
1688
+ noteDecisionOutOfSet(ctx, ledger, ran, decision, toolName, "hook ruling not honored (closed word set is allow|deny|ask); this call falls back to the engine gate");
1484
1689
  }
1485
1690
  }
1486
1691
  }
1487
1692
  }
1488
1693
  return { rewroteBy };
1489
1694
  };
1490
- for (const lane of hookLanesOf(pre)) {
1695
+ for (const lane of hookPhasesOf(pre)) {
1491
1696
  if (lane.phase === "unmanaged") {
1492
1697
  const pass = await runPass(lane.phase, lane.groups, onceFired);
1493
1698
  if (pass.deny !== undefined)
@@ -1511,38 +1716,45 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1511
1716
  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");
1512
1717
  }
1513
1718
  }
1719
+ const managedGap = ledger.managedVerdictGap;
1720
+ if (below !== undefined && !spoke && managedGap === undefined)
1721
+ return undefined;
1514
1722
  if (asked !== undefined) {
1515
- return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1723
+ return { action: "ask", message: asked, questionCause: "asked", ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
1516
1724
  }
1517
- const managedGap = ledger.managedVerdictGap;
1518
1725
  if (managedGap !== undefined) {
1519
1726
  return {
1520
1727
  action: "ask",
1521
1728
  message: "a managed PreToolUse hook did not run to a verdict — approval required",
1522
1729
  questionCause: managedGap,
1523
1730
  ...(rewritten ? { updatedInput: currentInput } : {}),
1731
+ ...realApproval,
1524
1732
  ...contextField(),
1525
1733
  };
1526
1734
  }
1735
+ if (prior.unreadable !== undefined) {
1736
+ return { action: "ask", message: prior.unreadable.message, questionCause: prior.unreadable.cause, ...(rewritten ? { updatedInput: currentInput } : {}), ...realApproval, ...contextField() };
1737
+ }
1527
1738
  if (unknownDecision) {
1528
1739
  return {
1529
1740
  action: "ask",
1530
1741
  message: "a PreToolUse hook returned an unrecognized permissionDecision — approval required",
1531
1742
  questionCause: "verdict_unreadable",
1532
1743
  ...(rewritten ? { updatedInput: currentInput } : {}),
1744
+ ...realApproval,
1533
1745
  ...contextField(),
1534
1746
  };
1535
1747
  }
1536
1748
  const carried = { ...(rewritten ? { updatedInput: currentInput } : {}), ...contextField() };
1537
1749
  if (allowedFor !== undefined && allowedFor.input === currentInput)
1538
1750
  return { action: "allow", ...carried };
1539
- if (rewritten || HOOK_PHASES.some((p) => contexts[p].length > 0))
1751
+ if (rewritten || HOOK_PHASES.some((p) => contexts[p].length > 0) || prior.additionalContext !== undefined)
1540
1752
  return carried;
1541
1753
  return undefined;
1542
1754
  };
1543
1755
  }
1544
1756
  if (post) {
1545
- translators.PostToolUse = async (e, signal) => {
1757
+ translators.PostToolUse = async (e, halt, below) => {
1546
1758
  const toolName = e.tool;
1547
1759
  const input = e.input;
1548
1760
  const output = e.output;
@@ -1559,7 +1771,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1559
1771
  tool_use_id: e.tool_use_id,
1560
1772
  };
1561
1773
  const ledger = createUnrunLedger(ctx, "PostToolUse", toolName);
1562
- const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, onceFired, signal, ledger);
1774
+ const singles = await runEventLanes("PostToolUse", post, toolName, payload, ctx, onceFired, halt, ledger);
1563
1775
  for (const ran of singles) {
1564
1776
  const { entry, run } = ran;
1565
1777
  if (run.code === 2) {
@@ -1592,12 +1804,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1592
1804
  }
1593
1805
  }
1594
1806
  }
1595
- const additionalContext = buildHookContext(contexts);
1596
- return additionalContext === undefined ? undefined : { additionalContext };
1807
+ if (contexts.length === 0)
1808
+ return undefined;
1809
+ return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1597
1810
  };
1598
1811
  }
1599
1812
  if (postFailure) {
1600
- translators.PostToolUseFailure = async (e, signal) => {
1813
+ translators.PostToolUseFailure = async (e, halt, below) => {
1601
1814
  const toolName = e.tool;
1602
1815
  const f = e.failure;
1603
1816
  const contexts = [];
@@ -1611,7 +1824,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1611
1824
  is_interrupt: f.isInterrupt,
1612
1825
  };
1613
1826
  const ledger = createUnrunLedger(ctx, "PostToolUseFailure", toolName);
1614
- const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired, signal, ledger);
1827
+ const singles = await runEventLanes("PostToolUseFailure", postFailure, toolName, payload, ctx, onceFired, halt, ledger);
1615
1828
  for (const ran of singles) {
1616
1829
  const { entry, run } = ran;
1617
1830
  if (run.code === 2) {
@@ -1640,12 +1853,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1640
1853
  contexts.push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
1641
1854
  }
1642
1855
  }
1643
- const additionalContext = buildHookContext(contexts);
1644
- return additionalContext === undefined ? undefined : { additionalContext };
1856
+ if (contexts.length === 0)
1857
+ return undefined;
1858
+ return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1645
1859
  };
1646
1860
  }
1647
1861
  if (postBatch) {
1648
- translators.PostToolBatch = async (e, signal) => {
1862
+ translators.PostToolBatch = async (e, halt, below) => {
1649
1863
  const contexts = [];
1650
1864
  const payload = {
1651
1865
  ...basePayload(ctx, e.permissionMode),
@@ -1658,7 +1872,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1658
1872
  })),
1659
1873
  };
1660
1874
  const ledger = createUnrunLedger(ctx, "PostToolBatch", undefined);
1661
- const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, onceFired, signal, ledger);
1875
+ const singles = await runEventLanes("PostToolBatch", ignoreMatchers(postBatch), "", payload, ctx, onceFired, halt, ledger);
1662
1876
  for (const ran of singles) {
1663
1877
  const { entry, run } = ran;
1664
1878
  if (run.code === 2) {
@@ -1683,16 +1897,17 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1683
1897
  if (out.hookSpecificOutput !== undefined)
1684
1898
  ctx.logger.warn("hook_result_unsupported", { event: "PostToolBatch" });
1685
1899
  }
1686
- const additionalContext = buildHookContext(contexts);
1687
- return additionalContext === undefined ? undefined : { additionalContext };
1900
+ if (contexts.length === 0)
1901
+ return undefined;
1902
+ return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1688
1903
  };
1689
1904
  }
1690
1905
  if (promptSubmit) {
1691
- translators.UserPromptSubmit = async (e, signal) => {
1906
+ translators.UserPromptSubmit = async (e, halt, below) => {
1692
1907
  const contexts = [];
1693
1908
  const payload = { ...basePayload(ctx, e.permissionMode), hook_event_name: "UserPromptSubmit", prompt: clipDataText(e.prompt, MAX_TOOL_INPUT_CHARS) };
1694
1909
  const ledger = createUnrunLedger(ctx, "UserPromptSubmit", undefined);
1695
- const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, signal, ledger);
1910
+ const singles = await runEventLanes("UserPromptSubmit", ignoreMatchers(promptSubmit), "", payload, ctx, onceFired, halt, ledger);
1696
1911
  for (const ran of singles) {
1697
1912
  const { entry, run } = ran;
1698
1913
  if (run.code === 2) {
@@ -1723,12 +1938,13 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1723
1938
  contexts.push(clipDataText(h.additionalContext, MAX_HOOK_FEEDBACK_CHARS));
1724
1939
  }
1725
1940
  }
1726
- const additionalContext = buildHookContext(contexts);
1727
- return additionalContext === undefined ? undefined : { additionalContext };
1941
+ if (contexts.length === 0)
1942
+ return undefined;
1943
+ return { ...belowMembers(below), ...buildHookContextField(withBelowContext(contexts, below)) };
1728
1944
  };
1729
1945
  }
1730
1946
  if (stopEntries) {
1731
- translators.Stop = async (sctx, signal) => {
1947
+ translators.Stop = async (sctx, halt, below) => {
1732
1948
  const payload = { ...basePayload(ctx, sctx.permissionMode), hook_event_name: "Stop", stop_hook_active: sctx.stopHookActive };
1733
1949
  let llmExtra;
1734
1950
  const wantsPrompt = stopEntries.some((g) => g.hooks.some((h) => h.type === "prompt"));
@@ -1766,7 +1982,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1766
1982
  const effectiveGroups = llmExtra || !wantsPrompt
1767
1983
  ? stopGroups
1768
1984
  : stopGroups.map((g) => ({ ...g, hooks: g.hooks.filter((h) => h.type !== "prompt") })).filter((g) => g.hooks.length > 0);
1769
- const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, onceFired, signal, ledger, llmExtra);
1985
+ const singles = await runEventLanes("Stop", effectiveGroups, "", payload, ctx, onceFired, halt, ledger, llmExtra);
1770
1986
  let block;
1771
1987
  let sawContinueFalse = false;
1772
1988
  const contexts = [];
@@ -1821,13 +2037,14 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1821
2037
  if (block === undefined && contexts.length === 0)
1822
2038
  return undefined;
1823
2039
  return {
2040
+ ...belowMembers(below),
1824
2041
  ...(block !== undefined ? { block } : {}),
1825
- ...buildHookContextField(contexts),
2042
+ ...buildHookContextField(withBelowContext(contexts, below)),
1826
2043
  };
1827
2044
  };
1828
2045
  }
1829
2046
  if (stopFailureEntries) {
1830
- translators.StopFailure = async (fctx, signal) => {
2047
+ translators.StopFailure = async (fctx, halt) => {
1831
2048
  const turnError = toAssistantTurnError(fctx.errorKind);
1832
2049
  const payload = {
1833
2050
  ...basePayload(ctx, undefined),
@@ -1835,11 +2052,11 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1835
2052
  error: turnError,
1836
2053
  ...(fctx.error ? { error_details: clipDataText(redactSecrets(fctx.error), MAX_HOOK_FEEDBACK_CHARS) } : {}),
1837
2054
  };
1838
- await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired, signal);
2055
+ await runObserveOnlyEvent("StopFailure", stopFailureEntries, turnError, payload, ctx, onceFired, halt);
1839
2056
  };
1840
2057
  }
1841
2058
  if (preCompactEntries) {
1842
- translators.PreCompact = async (pctx, signal) => {
2059
+ translators.PreCompact = async (pctx, halt, below) => {
1843
2060
  const payload = {
1844
2061
  ...basePayload(ctx, undefined),
1845
2062
  hook_event_name: "PreCompact",
@@ -1847,11 +2064,11 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1847
2064
  custom_instructions: pctx.customInstructions ?? null,
1848
2065
  };
1849
2066
  const ledger = createUnrunLedger(ctx, "PreCompact", undefined);
1850
- const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired, signal, ledger);
2067
+ const singles = await runEventLanes("PreCompact", preCompactEntries, pctx.trigger, payload, ctx, onceFired, halt, ledger);
1851
2068
  for (const ran of singles) {
1852
2069
  const { entry, run } = ran;
1853
2070
  if (run.code === 2) {
1854
- return { block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
2071
+ return { ...belowMembers(below), block: clipDataText(run.stderr.trim(), MAX_HOOK_FEEDBACK_CHARS) || "blocked by a PreCompact hook" };
1855
2072
  }
1856
2073
  if (run.code !== 0) {
1857
2074
  noteExitNonzero(ctx, ledger, "PreCompact", ran);
@@ -1866,31 +2083,31 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
1866
2083
  ctx.logger.warn("hook_continue_false_unsupported", { event: "PreCompact" });
1867
2084
  }
1868
2085
  if (out.decision === "block") {
1869
- return { block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) };
2086
+ return { ...belowMembers(below), block: clipDataText(typeof out.reason === "string" ? out.reason : "blocked by a PreCompact hook", MAX_HOOK_FEEDBACK_CHARS) };
1870
2087
  }
1871
2088
  }
1872
2089
  return undefined;
1873
2090
  };
1874
2091
  }
1875
2092
  if (postCompactEntries) {
1876
- translators.PostCompact = async (cctx, signal) => {
2093
+ translators.PostCompact = async (cctx, halt) => {
1877
2094
  const payload = {
1878
2095
  ...basePayload(ctx, undefined),
1879
2096
  hook_event_name: "PostCompact",
1880
2097
  trigger: cctx.trigger,
1881
2098
  compact_summary: clipDataText(cctx.summary, MAX_TOOL_RESPONSE_CHARS),
1882
2099
  };
1883
- await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired, signal);
2100
+ await runObserveOnlyEvent("PostCompact", postCompactEntries, cctx.trigger, payload, ctx, onceFired, halt);
1884
2101
  };
1885
2102
  }
1886
2103
  const events = Object.keys(translators);
1887
2104
  return {
1888
- name: SETTINGS_HOOKS_REGISTRATION,
2105
+ name: SETTINGS_HOOKS_REGISTRATIONS[lane],
1889
2106
  uses: { events: events.flatMap((event) => SETTINGS_HOOK_STATIONS[event]), calls: [] },
1890
2107
  budgetMs,
1891
2108
  register(on) {
1892
2109
  for (const event of events)
1893
- hangTranslator(on, event, translators);
2110
+ hangTranslator(on, event, translators, lane);
1894
2111
  },
1895
2112
  };
1896
2113
  }