@sema-agent/core 7.51.2 → 7.52.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 (363) hide show
  1. package/CHANGELOG.md +504 -2
  2. package/dist/agents/agent-transcript-tool.d.ts +1 -1
  3. package/dist/agents/cascade.js +4 -0
  4. package/dist/agents/cumulative-stats.d.ts +3 -0
  5. package/dist/agents/cumulative-stats.js +3 -0
  6. package/dist/agents/list-agents-tool.d.ts +1 -1
  7. package/dist/agents/read-inbox-tool.d.ts +1 -1
  8. package/dist/agents/repair-loop.js +4 -0
  9. package/dist/agents/send-message-tool.d.ts +11 -16
  10. package/dist/agents/send-message-tool.js +11 -13
  11. package/dist/agents/subagent.js +6 -4
  12. package/dist/agents/tool-filter.d.ts +40 -13
  13. package/dist/agents/tool-filter.js +28 -14
  14. package/dist/bench/metrics.d.ts +1 -0
  15. package/dist/brain/anthropic.js +16 -12
  16. package/dist/brain/model-presets.d.ts +4 -5
  17. package/dist/brain/model-presets.js +4 -4
  18. package/dist/brain/open-responses.js +16 -12
  19. package/dist/brain/openai.js +16 -12
  20. package/dist/brain/request-params.d.ts +12 -33
  21. package/dist/brain/request-params.js +5 -2
  22. package/dist/brain/request-route.d.ts +9 -8
  23. package/dist/brain/request-route.js +1 -0
  24. package/dist/brain/route-adjudicator.d.ts +117 -47
  25. package/dist/brain/route-adjudicator.js +171 -42
  26. package/dist/brain/route-conformance.js +2 -2
  27. package/dist/brain/status-sink.d.ts +18 -0
  28. package/dist/brain/status-sink.js +7 -0
  29. package/dist/core/allow-entry.d.ts +40 -8
  30. package/dist/core/allow-entry.js +23 -16
  31. package/dist/core/auto-compaction.d.ts +23 -20
  32. package/dist/core/auto-compaction.js +37 -16
  33. package/dist/core/auto-promote.d.ts +2 -1
  34. package/dist/core/auto-promote.js +2 -2
  35. package/dist/core/caller-tool-name.d.ts +9 -0
  36. package/dist/core/caller-tool-name.js +13 -0
  37. package/dist/core/checkpoint-requirements.d.ts +7 -1
  38. package/dist/core/checkpoint-requirements.js +2 -0
  39. package/dist/core/checkpoint-store.d.ts +36 -12
  40. package/dist/core/checkpoint-store.js +1 -1
  41. package/dist/core/child-clamp-seats.d.ts +6 -0
  42. package/dist/core/child-clamp-seats.js +2 -1
  43. package/dist/core/credential-vocabulary.d.ts +33 -8
  44. package/dist/core/credential-vocabulary.js +32 -7
  45. package/dist/core/decision-receipt.d.ts +2 -2
  46. package/dist/core/delegation-frames.d.ts +9 -2
  47. package/dist/core/delegation-name.d.ts +18 -0
  48. package/dist/core/delegation-name.js +47 -0
  49. package/dist/core/engine-capabilities.d.ts +8 -1
  50. package/dist/core/engine-capabilities.js +1 -1
  51. package/dist/core/engine-notice.d.ts +36 -19
  52. package/dist/core/frontmatter-fence.d.ts +10 -0
  53. package/dist/core/frontmatter-fence.js +6 -0
  54. package/dist/core/gate-fold.js +5 -23
  55. package/dist/core/gate-lanes.js +356 -270
  56. package/dist/core/governance-codes.d.ts +9 -4
  57. package/dist/core/governance-codes.js +9 -3
  58. package/dist/core/hooks.d.ts +276 -734
  59. package/dist/core/hooks.js +97 -192
  60. package/dist/{tools/fs → core}/image-admission.d.ts +21 -3
  61. package/dist/{tools/fs → core}/image-admission.js +24 -6
  62. package/dist/core/mcp-connection-pool.d.ts +4 -1
  63. package/dist/core/mcp-connection-pool.js +7 -2
  64. package/dist/core/mcp-probe.d.ts +12 -9
  65. package/dist/core/mcp-probe.js +2 -2
  66. package/dist/core/mcp-server-spec.d.ts +16 -0
  67. package/dist/core/mcp.d.ts +11 -7
  68. package/dist/core/mcp.js +11 -5
  69. package/dist/core/memory-engine/data-plane.js +5 -1
  70. package/dist/core/memory-engine/distiller.js +16 -2
  71. package/dist/core/memory-engine/engine.d.ts +8 -11
  72. package/dist/core/memory-engine/engine.js +9 -5
  73. package/dist/core/memory-engine/frontmatter.d.ts +6 -0
  74. package/dist/core/memory-engine/frontmatter.js +19 -16
  75. package/dist/core/memory.d.ts +2 -2
  76. package/dist/core/mode-policy.d.ts +4 -0
  77. package/dist/core/mode-policy.js +7 -5
  78. package/dist/core/model-seat.d.ts +4 -1
  79. package/dist/core/mods/dollar.d.ts +109 -0
  80. package/dist/core/mods/dollar.js +154 -0
  81. package/dist/core/mods/host.d.ts +23 -0
  82. package/dist/core/mods/host.js +21 -0
  83. package/dist/core/mods/kernel/budget.d.ts +43 -0
  84. package/dist/core/mods/kernel/budget.js +83 -0
  85. package/dist/core/mods/kernel/dispatch.d.ts +100 -0
  86. package/dist/core/mods/kernel/dispatch.js +701 -0
  87. package/dist/core/mods/kernel/events.d.ts +242 -0
  88. package/dist/core/mods/kernel/events.js +210 -0
  89. package/dist/core/mods/kernel/index.d.ts +6 -0
  90. package/dist/core/mods/kernel/index.js +5 -0
  91. package/dist/core/mods/kernel/receive.d.ts +65 -0
  92. package/dist/core/mods/kernel/receive.js +123 -0
  93. package/dist/core/mods/kernel/registration.d.ts +131 -0
  94. package/dist/core/mods/kernel/registration.js +102 -0
  95. package/dist/core/mods/kernel/site-rules.d.ts +84 -0
  96. package/dist/core/mods/kernel/site-rules.js +104 -0
  97. package/dist/core/mods/stations.d.ts +350 -0
  98. package/dist/core/mods/stations.js +444 -0
  99. package/dist/core/own-property-receipt.js +11 -23
  100. package/dist/core/parent-spec-seat.d.ts +18 -7
  101. package/dist/core/parent-spec-seat.js +6 -2
  102. package/dist/core/path-identity.js +2 -1
  103. package/dist/core/permission-rule-syntax.d.ts +15 -0
  104. package/dist/core/permission-rule-syntax.js +24 -16
  105. package/dist/core/permission-rules.d.ts +7 -1
  106. package/dist/core/permission-rules.js +26 -15
  107. package/dist/core/prompt-section-seat.d.ts +15 -27
  108. package/dist/core/prompt-section-seat.js +73 -40
  109. package/dist/core/protocol-http-auth.d.ts +3 -1
  110. package/dist/core/protocol-http-auth.js +2 -0
  111. package/dist/core/reminder-mint.d.ts +2 -2
  112. package/dist/core/remote-env.d.ts +24 -12
  113. package/dist/core/remote-env.js +1 -0
  114. package/dist/core/resume-startup-cause.d.ts +16 -1
  115. package/dist/core/resume-startup-cause.js +4 -1
  116. package/dist/core/retired-keys.d.ts +4 -0
  117. package/dist/core/retired-keys.js +3 -0
  118. package/dist/core/runner/announce-once-ledger.d.ts +31 -1
  119. package/dist/core/runner/announce-once-ledger.js +22 -0
  120. package/dist/core/runner/assemble-result.d.ts +9 -0
  121. package/dist/core/runner/assemble-result.js +3 -1
  122. package/dist/core/runner/call-hook-stations.d.ts +23 -27
  123. package/dist/core/runner/call-hook-stations.js +58 -42
  124. package/dist/core/runner/caller-stop-gate.d.ts +25 -0
  125. package/dist/core/runner/caller-stop-gate.js +7 -0
  126. package/dist/core/runner/compaction-seams.d.ts +8 -22
  127. package/dist/core/runner/compaction-seams.js +28 -53
  128. package/dist/core/runner/construction-doors.js +11 -1
  129. package/dist/core/runner/contracts.d.ts +85 -83
  130. package/dist/core/runner/credential-origin-seat.d.ts +2 -0
  131. package/dist/core/runner/credential-origin-seat.js +19 -0
  132. package/dist/core/runner/extra-body-dropped-seat.d.ts +2 -6
  133. package/dist/core/runner/gate-exit.d.ts +47 -36
  134. package/dist/core/runner/gate-exit.js +8 -2
  135. package/dist/core/runner/image.js +1 -2
  136. package/dist/core/runner/inline-image.js +1 -1
  137. package/dist/core/runner/notice-seat-input.d.ts +11 -0
  138. package/dist/core/runner/notice-seat-input.js +1 -0
  139. package/dist/core/runner/person-path-rows.js +1 -0
  140. package/dist/core/runner/prepare-acquire-reconcile.js +5 -1
  141. package/dist/core/runner/prepare-ask-lane.d.ts +5 -7
  142. package/dist/core/runner/prepare-ask-lane.js +6 -15
  143. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
  144. package/dist/core/runner/prepare-caps-and-workflow.js +25 -8
  145. package/dist/core/runner/prepare-config-doors.d.ts +5 -0
  146. package/dist/core/runner/prepare-config-doors.js +9 -0
  147. package/dist/core/runner/prepare-defer-classify.js +3 -4
  148. package/dist/core/runner/prepare-gate-stations.d.ts +8 -11
  149. package/dist/core/runner/prepare-gate-stations.js +11 -10
  150. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -12
  151. package/dist/core/runner/prepare-inherited-gate.js +8 -21
  152. package/dist/core/runner/prepare-memory-engine-session.js +1 -1
  153. package/dist/core/runner/prepare-park-ask.js +10 -14
  154. package/dist/core/runner/prepare-policy-chain.d.ts +17 -15
  155. package/dist/core/runner/prepare-policy-chain.js +95 -100
  156. package/dist/core/runner/prepare-project-context.js +7 -10
  157. package/dist/core/runner/prepare-prompt-assembly.d.ts +45 -40
  158. package/dist/core/runner/prepare-prompt-assembly.js +134 -101
  159. package/dist/core/runner/prepare-prompt-inputs.d.ts +18 -19
  160. package/dist/core/runner/prepare-prompt-inputs.js +5 -7
  161. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -4
  162. package/dist/core/runner/prepare-protocol-tools.js +2 -2
  163. package/dist/core/runner/prepare-safety-scan.js +6 -10
  164. package/dist/core/runner/prepare-suspend-saga.d.ts +6 -0
  165. package/dist/core/runner/prepare-suspend-saga.js +7 -5
  166. package/dist/core/runner/prepare-task.js +33 -15
  167. package/dist/core/runner/prepare-turn-wiring.d.ts +13 -15
  168. package/dist/core/runner/prepare-turn-wiring.js +29 -27
  169. package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -6
  170. package/dist/core/runner/prepare-wiring-manifest.js +7 -7
  171. package/dist/core/runner/resume-apply.d.ts +5 -2
  172. package/dist/core/runner/resume-apply.js +24 -42
  173. package/dist/core/runner/resume-checkpoint-screen.d.ts +1 -0
  174. package/dist/core/runner/resume-checkpoint-screen.js +9 -1
  175. package/dist/core/runner/resume-preflight.js +38 -16
  176. package/dist/core/runner/retired-runner-deps.js +3 -1
  177. package/dist/core/runner/run-brain-sinks.d.ts +3 -2
  178. package/dist/core/runner/run-brain-sinks.js +5 -1
  179. package/dist/core/runner/run-compaction-machinery.d.ts +7 -1
  180. package/dist/core/runner/run-compaction-machinery.js +7 -33
  181. package/dist/core/runner/run-harness-handlers.js +4 -2
  182. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  183. package/dist/core/runner/run-identity-wiring.js +3 -2
  184. package/dist/core/runner/run-leg.js +62 -84
  185. package/dist/core/runner/run-notification-lane.js +1 -0
  186. package/dist/core/runner/run-recovery-lanes.d.ts +3 -0
  187. package/dist/core/runner/run-recovery-lanes.js +11 -10
  188. package/dist/core/runner/run-settle-and-teardown.js +9 -28
  189. package/dist/core/runner/run-stop-and-final-verify.js +18 -25
  190. package/dist/core/runner/run-terminal-adoption.d.ts +3 -0
  191. package/dist/core/runner/run-terminal-adoption.js +33 -22
  192. package/dist/core/runner/run-tool-mount-facts.js +5 -3
  193. package/dist/core/runner/run-turn-boundary.js +23 -34
  194. package/dist/core/runner/runtask.d.ts +6 -7
  195. package/dist/core/runner/runtask.js +14 -72
  196. package/dist/core/runner/session-rule-policy.d.ts +5 -5
  197. package/dist/core/runner/session-rule-policy.js +13 -8
  198. package/dist/core/runner/side-call-account.d.ts +68 -0
  199. package/dist/core/runner/side-call-account.js +61 -0
  200. package/dist/core/runner/stream-halt-verbs.js +12 -17
  201. package/dist/core/runner/stream-lifecycle-verbs.js +8 -5
  202. package/dist/core/runner/stream-steer-verb.d.ts +1 -1
  203. package/dist/core/runner/stream-steer-verb.js +20 -38
  204. package/dist/core/runner/sub-agent-deny-lists.d.ts +121 -22
  205. package/dist/core/runner/sub-agent-deny-lists.js +228 -71
  206. package/dist/core/runner/tool-end-body.d.ts +5 -6
  207. package/dist/core/runner/tool-end-body.js +6 -4
  208. package/dist/core/runner/tool-not-executed.d.ts +15 -0
  209. package/dist/core/runner/tool-not-executed.js +30 -0
  210. package/dist/core/runner/tool-occurrence.d.ts +8 -7
  211. package/dist/core/runner/tool-occurrence.js +5 -5
  212. package/dist/core/runner/tool-result-media-seat.d.ts +1 -1
  213. package/dist/core/runner-deps.d.ts +20 -28
  214. package/dist/core/safety-merge-corpus.js +18 -2
  215. package/dist/core/secret-env.d.ts +88 -20
  216. package/dist/core/secret-env.js +192 -38
  217. package/dist/core/session-policy-store.d.ts +52 -16
  218. package/dist/core/session-policy-store.js +118 -28
  219. package/dist/core/session-reconcile.d.ts +4 -0
  220. package/dist/core/session-reconcile.js +2 -1
  221. package/dist/core/shell-read-silence.js +3 -32
  222. package/dist/core/side-query.d.ts +5 -3
  223. package/dist/core/skill-grant.d.ts +3 -2
  224. package/dist/core/skill-grant.js +17 -9
  225. package/dist/core/skills-directory.d.ts +31 -11
  226. package/dist/core/skills-directory.js +655 -104
  227. package/dist/core/task-event.d.ts +13 -3
  228. package/dist/core/task-result.d.ts +8 -0
  229. package/dist/core/task-spec.d.ts +20 -19
  230. package/dist/core/task-stream.d.ts +14 -4
  231. package/dist/core/tighten-task-spec.d.ts +4 -3
  232. package/dist/core/tighten-task-spec.js +2 -4
  233. package/dist/core/tool-check-reading.d.ts +74 -0
  234. package/dist/core/tool-check-reading.js +80 -0
  235. package/dist/core/tool-policy.d.ts +59 -18
  236. package/dist/core/tool-policy.js +77 -12
  237. package/dist/core/tool-registry.d.ts +20 -0
  238. package/dist/core/tool-registry.js +4 -0
  239. package/dist/core/tool-spec.d.ts +10 -6
  240. package/dist/core/tools.js +2 -0
  241. package/dist/core/trace.d.ts +51 -32
  242. package/dist/core/unc-spelling.d.ts +10 -0
  243. package/dist/core/unc-spelling.js +3 -0
  244. package/dist/core/untrusted-egress.d.ts +8 -0
  245. package/dist/core/untrusted-egress.js +4 -1
  246. package/dist/core/user-attachment.d.ts +12 -2
  247. package/dist/core/user-attachment.js +17 -2
  248. package/dist/core/wiring-manifest.d.ts +14 -13
  249. package/dist/core/wiring-manifest.js +2 -2
  250. package/dist/engine/compaction/utils.js +1 -2
  251. package/dist/engine/execution-env/node-execution-env.d.ts +4 -3
  252. package/dist/engine/execution-env/symlink-chain.js +1 -1
  253. package/dist/engine/harness/agent-harness.d.ts +1 -1
  254. package/dist/engine/harness/agent-harness.js +3 -3
  255. package/dist/engine/harness/types.d.ts +17 -16
  256. package/dist/engine/harness/types.js +7 -1
  257. package/dist/engine/llm/types.d.ts +12 -7
  258. package/dist/engine/loop/agent-loop.js +1 -1
  259. package/dist/engine/loop/types.d.ts +1 -1
  260. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  261. package/dist/engine/session/import-validate.js +3 -10
  262. package/dist/engine/session/session.d.ts +4 -4
  263. package/dist/engine/session/session.js +4 -4
  264. package/dist/index.d.ts +20 -16
  265. package/dist/index.js +13 -11
  266. package/dist/internal/harness.d.ts +1 -1
  267. package/dist/internal/harness.js +1 -1
  268. package/dist/orchestration/run-spec.d.ts +4 -6
  269. package/dist/orchestration/run-spec.js +39 -63
  270. package/dist/orchestration/run-workflow-tool.js +1 -2
  271. package/dist/orchestration/workflow.d.ts +11 -3
  272. package/dist/orchestration/workflow.js +11 -2
  273. package/dist/prompt-assembly/artifact-store.d.ts +37 -56
  274. package/dist/prompt-assembly/artifact-store.js +31 -49
  275. package/dist/prompt-assembly/artifact.d.ts +89 -76
  276. package/dist/prompt-assembly/artifact.js +167 -146
  277. package/dist/prompt-assembly/assemble.d.ts +34 -29
  278. package/dist/prompt-assembly/assemble.js +33 -220
  279. package/dist/prompt-assembly/bands.d.ts +20 -0
  280. package/dist/prompt-assembly/bands.js +18 -0
  281. package/dist/prompt-assembly/catalog/constitution.d.ts +23 -0
  282. package/dist/prompt-assembly/catalog/constitution.js +69 -0
  283. package/dist/prompt-assembly/catalog/core-behavior.d.ts +9 -0
  284. package/dist/prompt-assembly/catalog/core-behavior.js +85 -0
  285. package/dist/prompt-assembly/catalog/index.d.ts +11 -0
  286. package/dist/prompt-assembly/catalog/index.js +59 -0
  287. package/dist/prompt-assembly/catalog/modes.d.ts +15 -0
  288. package/dist/prompt-assembly/catalog/modes.js +137 -0
  289. package/dist/prompt-assembly/catalog/role.d.ts +10 -0
  290. package/dist/prompt-assembly/catalog/role.js +157 -0
  291. package/dist/prompt-assembly/catalog/schema.d.ts +124 -0
  292. package/dist/prompt-assembly/catalog/schema.js +25 -0
  293. package/dist/prompt-assembly/catalog/sections.d.ts +9 -0
  294. package/dist/prompt-assembly/catalog/sections.js +98 -0
  295. package/dist/prompt-assembly/catalog/team.d.ts +4 -0
  296. package/dist/prompt-assembly/catalog/team.js +258 -0
  297. package/dist/prompt-assembly/composer.d.ts +24 -28
  298. package/dist/prompt-assembly/composer.js +124 -46
  299. package/dist/prompt-assembly/epoch.d.ts +89 -79
  300. package/dist/prompt-assembly/epoch.js +115 -142
  301. package/dist/prompt-assembly/explain.d.ts +58 -34
  302. package/dist/prompt-assembly/explain.js +21 -24
  303. package/dist/prompt-assembly/overlay.d.ts +127 -0
  304. package/dist/prompt-assembly/overlay.js +301 -0
  305. package/dist/prompt-assembly/renderers.d.ts +21 -0
  306. package/dist/prompt-assembly/renderers.js +39 -0
  307. package/dist/prompt-assembly/section-usage.d.ts +3 -3
  308. package/dist/prompt-assembly/section-usage.js +1 -1
  309. package/dist/prompt-assembly/select.d.ts +56 -0
  310. package/dist/prompt-assembly/select.js +69 -0
  311. package/dist/prompt-assembly/sugar.d.ts +31 -0
  312. package/dist/prompt-assembly/sugar.js +20 -0
  313. package/dist/prompt-assembly/turn-snapshot.d.ts +3 -2
  314. package/dist/prompt-assembly/types.d.ts +45 -148
  315. package/dist/prompts/coordinator.d.ts +2 -21
  316. package/dist/prompts/coordinator.js +3 -255
  317. package/dist/prompts/cowork.d.ts +27 -16
  318. package/dist/prompts/cowork.js +2 -23
  319. package/dist/prompts/default.d.ts +38 -500
  320. package/dist/prompts/default.js +20 -285
  321. package/dist/prompts/supervisor.d.ts +6 -71
  322. package/dist/prompts/supervisor.js +6 -117
  323. package/dist/tools/fs/bash-readonly-classifier.d.ts +6 -5
  324. package/dist/tools/fs/dir-entries.d.ts +4 -2
  325. package/dist/tools/fs/dir-entries.js +16 -4
  326. package/dist/tools/fs/fs-bash.d.ts +1 -1
  327. package/dist/tools/fs/fs-bash.js +22 -11
  328. package/dist/tools/fs/fs-pdf.js +17 -13
  329. package/dist/tools/fs/fs-read.js +4 -3
  330. package/dist/tools/fs/fs-shared.d.ts +1 -10
  331. package/dist/tools/fs/fs-shared.js +3 -4
  332. package/dist/tools/fs/fs-write.js +2 -1
  333. package/dist/tools/fs/notebook.js +1 -1
  334. package/dist/tools/fs/pdf.d.ts +20 -10
  335. package/dist/tools/fs/pdf.js +19 -10
  336. package/dist/tools/fs/program-read-model.d.ts +68 -8
  337. package/dist/tools/fs/program-read-model.js +112 -33
  338. package/dist/tools/fs/program-read-model.rows.js +30 -10
  339. package/dist/tools/fs/read-deny-home.d.ts +25 -9
  340. package/dist/tools/fs/read-deny-home.js +28 -27
  341. package/dist/tools/fs/read-deny.d.ts +41 -11
  342. package/dist/tools/fs/read-deny.js +51 -5
  343. package/dist/tools/fs/safety.d.ts +4 -26
  344. package/dist/tools/fs/safety.js +10 -32
  345. package/dist/tools/fs/search.js +15 -10
  346. package/dist/tools/fs/shell-read-argv.d.ts +3 -2
  347. package/dist/tools/fs/shell-read-argv.js +118 -38
  348. package/dist/tools/fs/shell-read-establish.d.ts +5 -5
  349. package/dist/tools/fs/shell-read-establish.js +11 -61
  350. package/dist/tools/fs/shell-read-identity.js +6 -3
  351. package/dist/tools/fs/shell-read-judge.d.ts +13 -15
  352. package/dist/tools/fs/shell-read-judge.js +18 -4
  353. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  354. package/dist/tools/web.js +1 -1
  355. package/package.json +13 -1
  356. package/test/export-surface.snapshot.json +242 -99
  357. package/wire-changes/core@7.52.0.json +5859 -0
  358. package/dist/core/hook-seats.d.ts +0 -66
  359. package/dist/core/hook-seats.js +0 -37
  360. package/dist/prompt-assembly/packs/sema-default.d.ts +0 -2
  361. package/dist/prompt-assembly/packs/sema-default.js +0 -147
  362. package/dist/prompts/simple-sections.d.ts +0 -58
  363. package/dist/prompts/simple-sections.js +0 -43
@@ -5,461 +5,213 @@ import type { CallTargetStamp } from "./effective-path-target.js";
5
5
  import type { GateOutcome } from "./gate-outcome.js";
6
6
  import { type PreToolUseScreening } from "./runner/gate-exit.js";
7
7
  export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
8
- export { cloneObserverInput } from "./runner/gate-exit.js";
9
8
  import { type AskClass } from "./ask-class.js";
10
9
  import { type PermissionMode } from "./ask-origin.js";
11
10
  import { type AutoModeDenialTracker } from "./auto-mode.js";
12
11
  export { formatHookFeedback } from "./reminder-mint.js";
13
12
  import type { WiringLegKind } from "./wiring-manifest.js";
14
- import { type SafeNotifier } from "./safe-notify.js";
15
13
  import type { PermissionSeatInputs } from "./mode-policy.js";
16
14
  import type { SkillGateSeats } from "./skill-grant.js";
17
15
  import type { AllowEntryGateSeats } from "./allow-entry.js";
18
16
  /**
19
- * In-process hook seam (design/37) — a provider-agnostic interception layer modeled on CC's hooks,
20
- * kept process-internal (no shell/HTTP executors, no settings files). The original tool-call trio:
17
+ * The classic stations' faces and the two-phase tool gate. A deployment extends the engine through the mods bus
18
+ * (`RunnerDeps.mods` / `TaskSpec.mods`, `src/core/mods/`): each sema station raises one `classic.*` event, and the
19
+ * types here are those events' `e` shapes and answer forms. All feedback the model should see (a block reason,
20
+ * injected context) is wrapped as a `<system-reminder>` via {@link formatHookFeedback} so the model can read and
21
+ * self-correct.
21
22
  *
22
- * - {@link Hooks.preToolUse} runs before a tool executes. It may **rewrite** the args (an `allow` with
23
- * `updatedInput`), **restrict** the call (`deny`/`ask`), and/or inject `additionalContext`. It is a
24
- * pre-filter only: a hook's `allow` does NOT circumvent the tool-policy gate — the policy still runs on
25
- * the (possibly rewritten) args and has the final say (load-bearing invariant; see {@link runToolGate}).
26
- * - {@link Hooks.postToolUse} runs after a tool executes. It may replace the result content
27
- * (`updatedOutput`) and/or append `additionalContext`.
28
- * - {@link Hooks.userPromptSubmit} runs before the objective becomes a user message. It may `block`
29
- * submission (the task fails with a model-readable reason) or inject `additionalContext` ahead of it.
30
- *
31
- * The seam has since grown well past the trio — each later member carries its own contract below:
32
- * {@link Hooks.stop} / {@link Hooks.stopFailure} (run-end pushback / API-error terminal observer),
33
- * {@link Hooks.postToolUseFailure} and {@link Hooks.postToolBatch} (failure + batch-boundary taps),
34
- * {@link Hooks.preCompact} / {@link Hooks.postCompact} (compaction gate + observer),
35
- * {@link Hooks.permissionDenied} (deny observer), and the {@link Hooks.preToolUseObservational}
36
- * declaration flag.
37
- *
38
- * All feedback the model should see (a block reason, injected context) is wrapped as a
39
- * `<system-reminder>` via {@link formatHookFeedback} so the model can read and self-correct.
40
- *
41
- * **FAILURE POSTURE, and a correction to a widely-quoted anchor (recorded 2026-08-23).** Three repos
42
- * have reasoned from the sentence "CC fails open on hook failures, exit code 2 excepted". That sentence
43
- * is TRUE OF ONE LANE and false as a general statement, and the difference lands exactly on this seam.
44
- * CC runs hooks of seven kinds; the `command` kind is a shell script, and for it a spawn failure, a
45
- * non-zero exit and a per-hook timeout are all non-blocking — the call proceeds. But CC ALSO has an
46
- * in-process CALLBACK lane, which is the one structurally equivalent to this interface, and there a
47
- * PreToolUse callback that throws or times out is FAIL-CLOSED, with its own message stating that the
48
- * tool call was not executed. (Two further details the short sentence hides: the decision channel is
49
- * stdout JSON, with the exit code only a fallback — so exit 0 can block and a non-zero non-2 exit with
50
- * valid JSON does not; and the callback lane's UserPromptSubmit timeout BLOCKS the prompt.)
51
- *
52
- * So this seam's fail-closed postures are not a sema invention to be justified against CC — they are
53
- * the same posture CC takes on the same kind of lane: a `preToolUse` that throws or does not answer
54
- * refuses the call, a `userPromptSubmit` that throws or does not answer refuses the prompt, and the
55
- * OBSERVATION seats swallow and disclose because an observer has nothing to fail closed about. Anyone
56
- * quoting the short sentence to argue this layer "should" fail open is quoting the script lane.
23
+ * **FAILURE POSTURE, and a correction to a widely-quoted anchor (recorded 2026-08-23).** "CC fails open on hook
24
+ * failures, exit code 2 excepted" is TRUE OF ONE LANE: CC's `command` hooks (shell scripts) are non-blocking on a
25
+ * spawn failure, a non-zero exit or a timeout. CC's in-process CALLBACK lane — the one structurally equivalent to a
26
+ * handler here — is FAIL-CLOSED on PreToolUse (a callback that throws or times out does not execute the call) and
27
+ * its UserPromptSubmit timeout BLOCKS the prompt. So the two decision events here (`classic.PreToolUse`,
28
+ * `classic.UserPromptSubmit`) refuse on a handler's failure (the event table's failure column), and the observation
29
+ * events skip the failed handler, because an observer has nothing to fail closed about.
57
30
  */
58
- export interface Hooks {
59
- preToolUse?(toolName: string, input: unknown, ctx: HookToolContext): PreToolUseResult | undefined | Promise<PreToolUseResult | undefined>;
60
- /**
61
- * Deployment SELF-DECLARATION: this deployment's {@link preToolUse} is a pure OBSERVER — a tracer, an
62
- * audit sink, a metrics counter — that never judges a call. Declaring it changes exactly one thing:
63
- * the face stops being folded into the DELEGATION chain (`InheritedGate.parentConstraints`), so a
64
- * delegated child inherits no screening entry for it.
65
- *
66
- * Why the declaration exists. The delegation fold (issue #33) keys on PRESENCE — "a preToolUse is
67
- * wired" — because a callback cannot be inspected for whether it will ever return a verdict. That
68
- * reading is right for a screening face and wrong for an observer, and the difference is not free:
69
- * every inherited entry is one more OPAQUE constraint on the child, recorded on a durable park as
70
- * `parentConstraintCount`. A deployment that flips on a default-OFF diagnostic tracer therefore also
71
- * changes the shape every parked child's resume has to re-supply. The declaration is how a deployment
72
- * says which of the two it wired, on the same footing as every other thing the library trusts a
73
- * deployment to state about its own wiring.
74
- *
75
- * What does NOT change: the callback still runs in THIS task's own tool gate, on every call, with the
76
- * same context and the same crash posture (a throw is still the fail-closed deny). Observation is
77
- * untouched; only the inherited constraint goes away.
78
- *
79
- * The face also receives a DETACHED copy of the arguments rather than the live object, because the
80
- * gate reads a rewrite off reference identity and an in-place edit would otherwise be a second channel
81
- * that outlives the dropped return value. That detachment is BEST-EFFORT, in the same sense (and by the
82
- * same helper) as every other observe-only payload here: a graph `structuredClone` cannot take — a
83
- * function, a live handle — falls back to a shallow copy whose nested objects still alias, and a
84
- * `SharedArrayBuffer` shares its memory even when the clone succeeds. Phase-1 arguments are the model's
85
- * JSON on the ordinary path, which clones whole; the residual is reachable only through values a
86
- * DEPLOYMENT put there (its own rewrite, or driving `runToolGate` directly), and the face and the
87
- * deployment are one trust principal — this seam helps a deployment keep its own declaration, it is not
88
- * a boundary against an untrusted party.
89
- *
90
- * If a declared-observational face RETURNS SOMETHING ANYWAY, the return is REFUSED, not obeyed: the
91
- * call proceeds as if the hook had no opinion (`undefined`), and the deployment's `onError` face is
92
- * told (`phase:"hook"`, classification `observational-hook-verdict-ignored`). That covers the whole
93
- * result object — an `additionalContext` injection is a run-affecting capability too, so a face that
94
- * declared it only watches does not get to write into the model's transcript either. The refusal is
95
- * loud on purpose: a declaration that could be quietly contradicted would be a way to keep a
96
- * screening face's verdicts while shedding the delegation constraint they belong to.
97
- */
98
- preToolUseObservational?: true;
99
- postToolUse?(toolName: string, input: unknown, output: HookToolOutput, ctx: HookToolContext): PostToolUseResult | undefined | Promise<PostToolUseResult | undefined>;
100
- /**
101
- * design/373 §4.3 (D2) ⚠️ WIDENED INVOCATION SET: this seat now screens EVERY user-lane entrance,
102
- * not just the objective — a live `TaskStream.steer`, a wake resume's message (pre-CAS, at the
103
- * verb), and a parked steer's resume redelivery (any draining resume kind) all pass through it, discriminated by
104
- * {@link UserPromptSubmitContext.source}. A hook that blocks unconditionally therefore starts
105
- * refusing steers too — branch on `ctx.source` (+ `ctx.actor`) to scope a verdict. The screen's
106
- * domain is caller/operator PROSE only: engine-authored frames (task notifications, diagnostics,
107
- * recall) never pass here — external `notify()` text takes the sanitized DATA-frame lane instead
108
- * (ruled: notification frames are not prompts; see the notify contract). The same input can
109
- * legally arrive twice under two sources (live + parked redelivery) — idempotency is the hook's,
110
- * keyed on `ctx.inputId`. Block semantics per entrance: objective ⇒ the task ends `blocked`;
111
- * live steer ⇒ typed `steering.blocked_by_hook` to the steer caller; resume message ⇒ typed
112
- * pre-CAS refusal (the checkpoint stays pending); parked redelivery ⇒ the frame is withheld
113
- * with a `steering.parked_input_blocked` notice + a `delivery:"blocked"` human_input account,
114
- * and the resume itself proceeds (a blocked instruction must never wedge a wake).
115
- *
116
- * ⚠️ RE-ENTRANT within one leg: the steer verb goes live the moment the run's handle publishes —
117
- * BEFORE the objective seat runs — so a steer screen can overlap the objective screen (and a
118
- * resume-message screen, which fires pre-CAS at the verb) in flight. A stateful implementation
119
- * (per-call temp files, a single in-flight latch, a shared accumulator) must tolerate concurrent
120
- * entry; only steer-vs-steer is serialized. And that serialization has a LATENCY face: queued
121
- * steers screen strictly FIFO, so each waits behind its predecessor's ENTIRE hook bound (the
122
- * resolved `timeoutMs`, default 600s) — a slow or wedged screen delays every steer behind it by
123
- * up to the full bound, which is a reason to declare a short `timeoutMs` on deployments that
124
- * screen steers.
125
- */
126
- userPromptSubmit?(prompt: string,
127
- /** #281 件A (additive): the run/leg identity envelope — an implementation declaring only
128
- * `(prompt)` keeps working. Always supplied on the engine's emission. design/373: carries the
129
- * entrance discriminator (`source`/`inputId`/`actor`) — see {@link UserPromptSubmitContext}. */
130
- ctx?: UserPromptSubmitContext): UserPromptSubmitResult | undefined | Promise<UserPromptSubmitResult | undefined>;
131
- /**
132
- * roadmap #5 (CC 198 Stop hook, :473831-): runs when the agent WOULD OTHERWISE END its run (no
133
- * more tool calls, steering and follow-up queues dry). Return `{ block: reason }` to PUSH BACK —
134
- * the reason is injected as a model-readable message and the run continues another turn (external
135
- * adjudication: "the tests still fail", "the deliverable is missing X"). Return `undefined` to let
136
- * the run end. Guardrails (CC-exact): consecutive blocks are capped (default 8 — the loop then
137
- * overrides and ends with a warning); `ctx.stopHookActive` is true when the run is ALREADY
138
- * continuing due to a previous block — a hook that ignores it and blocks unconditionally loops
139
- * until the cap. `maxTurns` still outranks everything.
140
- */
141
- stop?(ctx: StopHookContext): StopHookResult | undefined | Promise<StopHookResult | undefined>;
142
- /**
143
- * design/134 (CC PostToolUseFailure parity, SDK 0.3.202): runs after a tool EXECUTION fails —
144
- * mutually exclusive with {@link postToolUse} (success → postToolUse; failure → this). Fires ONLY
145
- * for genuine execution failures: a gate/policy block, a plan-mode deny, a pre-execution abort or a
146
- * durable suspend triggers NEITHER callback (CC scopes those to the PermissionDenied event domain,
147
- * not implemented here). Capability is additionalContext-only (CC-exact): the text is appended to
148
- * the error tool result as a `<system-reminder>`; the failure itself cannot be rewritten.
149
- */
150
- postToolUseFailure?(toolName: string, input: unknown, failure: HookToolFailure, ctx: HookToolContext): PostToolUseFailureResult | undefined | Promise<PostToolUseFailureResult | undefined>;
151
- /**
152
- * design/134 (CC PostToolBatch parity, SDK 0.3.202): fires ONCE after every tool call in a batch
153
- * (one assistant turn's calls, design/120 stream-inline included) has resolved, before the next
154
- * model request. Per-tool postToolUse/postToolUseFailure fire first; the batch callback fires after,
155
- * at the turn boundary. A zero-tool turn does not fire; an aborted/steered-away boundary does not
156
- * fire (same gate family as design/133 attachments). additionalContext is injected once for the
157
- * whole batch as a boundary `<system-reminder>` (sanitized, shares the attachment byte cap).
158
- *
159
- * RB-467 (test-AI black-box repro, board 2305: ONE injection ate a whole subsequent batch): a batch
160
- * landing on a boundary where the engine itself injected (final-verification nudge / finalize
161
- * write-out) is now DELIVERED with `meta.injectedThisTurn` set, instead of being suppressed — for a
162
- * deployment observer, any suppressed boundary is lost audit data; attribution belongs to the
163
- * observer. On such a boundary the hook's `additionalContext` is IGNORED (the boundary already
164
- * carries an engine injection; stacking observer text there is the double-inject this gate
165
- * originally guarded against — that half of the old behavior stands).
166
- */
167
- postToolBatch?(batch: PostToolBatchCall[], meta?: {
168
- injectedThisTurn: "final_verification" | "finalize";
169
- },
170
- /** #281 件A (additive): the run/leg identity envelope, on its OWN parameter — `meta`'s presence
171
- * is an existing signal ("the engine injected at this boundary") that must not become
172
- * always-true just because identity rides along. Always supplied on the engine's emission. */
173
- ctx?: PostToolBatchContext): PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>;
174
- /**
175
- * design/134 (CC PreCompact parity): runs before each compaction, AFTER the trigger gate and a
176
- * valid cut point are confirmed (so every preCompact corresponds to a compaction that would
177
- * actually happen) and before the summary LLM call. `trigger`: "auto" (threshold) | "manual"
178
- * (/compact) | "forced" (promptTooLong recovery and trim-pressure — compaction is not optional
179
- * there). Return `{ block }` to skip this compaction (honored on auto/manual ONLY; ignored with a
180
- * trace on forced — blocking a compaction the provider already demanded would kill the run).
181
- * `additionalInstructions` is merged (appended) into the summarization instructions (CC
182
- * mergeHookInstructions shape). A THROWING preCompact is swallowed on every path (traced, treated
183
- * as no-block): an observer bug must never feed the compaction breaker or kill PTL recovery.
184
- */
185
- preCompact?(ctx: PreCompactContext): PreCompactResult | undefined | Promise<PreCompactResult | undefined>;
186
- /**
187
- * design/134 (CC PostCompact parity): observe-only, after the compaction landed (summary appended
188
- * to the session). No return capability (CC-exact); a throw is swallowed and traced.
189
- */
190
- postCompact?(ctx: PostCompactContext): void | Promise<void>;
191
- /**
192
- * StopFailure (the last engine-owned event of the registry-core 0.1.51 hooks contract; CC semantics:
193
- * "the turn ended because of an API error"). Fires ONCE, after the result is assembled, when the task
194
- * terminates `failed` BECAUSE the model/API layer errored (a brain error terminal —
195
- * auth / network / rate_limit / server_error / …). It does NOT fire for engine-owned failure classes:
196
- * budget, timeout/abort, max-turns, degenerate/walltime cutoffs (deliberate engine cuts), storage
197
- * conflict, blocked, or a durable suspend — those are not API errors. Observe-only (CC-exact: the
198
- * contract defines no StopFailure output capability); a throwing callback is swallowed + traced
199
- * (design/134 discipline — an observer bug must never rewrite a terminal state).
200
- */
201
- stopFailure?(ctx: StopFailureContext): void | Promise<void>;
202
- /**
203
- * design/134 R5 (CC PermissionDenied parity, SDK 0.3.202): runs when the tool gate's
204
- * adjudicate chain DENY-SHORT-CIRCUITS a tool call — a ToolPolicy deny, a gate tighten (egress /
205
- * irreversibility / coarse shellGate) whose `ask` resolved to deny (headless auto-deny included), or the
206
- * plan-mode write-deny. Closes the observation blind spot where a blocked call fires NEITHER postToolUse
207
- * NOR postToolUseFailure (R3 scoping). Does NOT fire for:
208
- * - a **durable suspend** (the ask path — CC surfaces that via a `can_use_tool` control_request);
209
- * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies circumvention canUseTool and are not
210
- * covered here").
211
- * DOES fire for a **PreToolUse hook CRASH** (RB-442, `source:"hook"`): a throwing hook is intercepted by
212
- * the ENGINE, which then makes its OWN fail-closed deny decision — likewise a hook result the decision
213
- * receipt REFUSES (out of contract, undetachable). The R5 exclusion above covers the hook's own DENY
214
- * DECISION — not an engine decision that merely happened near a hook.
215
- * Observe-only v1 (CC-exact payload `{toolName, input, toolCallId, reason}` + our `source` enum standing
216
- * in for CC `decision_reason_type`): no `retry` capability (recorded — retry semantics vs our adjudicate
217
- * chain need their own design), no additionalContext. A throwing callback is swallowed + traced via
218
- * `onError(phase:"hook")` — an observer bug must never alter the deny outcome (design/134 discipline).
219
- *
220
- * **HOW WIDE THIS SEAT IS, stated honestly (anchoring re-check 2026-08-23).** The exclusion sentence
221
- * above is quoted from the SDK's TYPE-LEVEL documentation, and the whole R5 scoping argument was
222
- * built on it without ever being checked against a shipped bundle. Against CC 2.1.223's runtime the
223
- * picture is different in a way that matters for how this comment may be USED: CC emits its
224
- * PermissionDenied event from exactly ONE site, guarded to the auto-mode CLASSIFIER's refusals —
225
- * a rule deny, a mode deny and a headless auto-deny emit nothing at all. So "a hook's own deny is
226
- * excluded" is TRIVIALLY true there (almost everything is excluded), and it must not be read as
227
- * evidence that this seat's TRIGGER SURFACE matches CC's. It does not: sema fires on four engine
228
- * emissions with seven `source` values, deliberately WIDER. The width is the useful direction for an
229
- * observation seat — the blind spot R3 named is real and a narrower seat would not close it — and it
230
- * is a divergence rather than a parity claim. The exclusion itself stays, on its own merit: a hook's
231
- * deny is the HOOK's decision, and this seat exists to attribute the ENGINE's.
232
- */
233
- permissionDenied?(payload: PermissionDeniedPayload): void | Promise<void>;
234
- /**
235
- * The TIME BOUND every seat on this object runs under, in milliseconds. Absent ⇒
236
- * {@link DEFAULT_HOOK_TIMEOUT_MS}. It is the analog of the CC hook record's own `timeout` field —
237
- * declared once per hook record there, once per hook OBJECT here, because this seam's record IS the
238
- * object. The unit is ms rather than CC's seconds: every other deadline this library takes ends in
239
- * `Ms`, and one unit per codebase beats one unit per upstream.
240
- *
241
- * **Why the seam has a bound at all.** Every callback above is DEPLOYMENT code awaited on the
242
- * engine's critical path, and one that never returns used to stop the whole run — the tool gate
243
- * awaited `preToolUse` bare, so a hung screening face left the call unsettled and the task's own
244
- * timeout/cancel could not end it. The diagnosis was an internal asymmetry rather than a guess: the
245
- * SAME callback installed as an inherited constraint on a delegated child was already bounded, because
246
- * that path folds it into a `ToolPolicy` and the policy wait is abort-raced — and the sibling seam in
247
- * this very file (`reversibilityProbe`) had already been given a default deadline for the identical
248
- * reason. Policies were bounded, probes were bounded, hooks were not.
249
- *
250
- * **What a bound does NOT change.** A seat that answers keeps its answer, byte for byte; a seat that
251
- * throws keeps its existing crash posture (this member changes nothing about a throw). The bound is
252
- * consulted only when a seat has not answered yet, and what happens then is per-seat and stated on
253
- * each member's own contract — decision seats fail CLOSED (`preToolUse` refuses the call the way a
254
- * crash does; `userPromptSubmit` refuses the prompt), observation seats are ABANDONED and the engine
255
- * proceeds as if the seat had returned nothing. Either way the deployment is told on its `onError`
256
- * lane, because a silently-dropped seat is the failure mode this member exists to make visible.
257
- *
258
- * **The seat also gets a signal.** An expired wait would otherwise leave the callback running with
259
- * nobody reading its answer — an orphan of the deployment's own making. So every context carries an
260
- * optional `signal` that aborts when this bound elapses (or the task aborts), and an implementation
261
- * that honors it can cancel the read it was waiting on. Honoring it is optional; a seat that ignores
262
- * it is exactly as bounded as before, the engine simply stops waiting.
263
- *
264
- * **WHAT THE BOUND IS ON, stated narrowly because the difference is not academic.** It bounds the
265
- * engine's WAIT, not the callback's CPU. A hook that blocks the thread SYNCHRONOUSLY — a spin loop, a
266
- * synchronous exec, a synchronous parse of something enormous — cannot be preempted by this bound or
267
- * by any other in-process mechanism, because the timer that would fire is queued behind the code that
268
- * is not yielding; the ordering of the timer's installation makes no difference to that. In-process
269
- * is the seam's whole premise (it is what makes a hook able to rewrite args and return a verdict),
270
- * and preemption is what it trades away. A deployment that must survive a hook it does not trust to
271
- * yield runs that hook somewhere with its own thread — a worker, a process, a service — and awaits
272
- * the result from here. What this member DOES bound is every asynchronous wait, which is where the
273
- * failure this was written for lives: a callback that awaits an unreachable host, a lock, a queue.
274
- *
275
- * **Value discipline** (bad values are loud, never silently reinterpreted): a non-finite, negative, or
276
- * above-`setTimeout`-ceiling (2147483647) value is REFUSED to the default and reported on the
277
- * deployment's error lane — the same treatment, and the same ceiling, the gate's `approvalTimeoutMs`
278
- * seat gives its own garbage. `0` is honored as written (every seat expires immediately), because a
279
- * deployment that writes zero has said something, and guessing that it meant "unbounded" would be the
280
- * one reading this member cannot support.
281
- */
282
- timeoutMs?: number;
31
+ /** The members every classic event's `e` carries for the leg it fires on. */
32
+ export interface ClassicEventBase {
33
+ /** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
34
+ readonly identity?: HookInvocationIdentity;
35
+ /** The delegated leg's id (its task id — the key its `task_progress` frames and its parent's fleet row use).
36
+ * Absent on a top-level leg. */
37
+ readonly agentId?: string;
38
+ /** The leg's EFFECTIVE permission mode when the event is raised — the mode seat's `observed` read (it arms no
39
+ * classifier). Absent where there is no leg seat: a host-driven {@link runToolGate}, the delegation-fold twin, the
40
+ * `"resume_message"` screen (before the resumed leg exists), and the compaction / StopFailure events (the
41
+ * reference's hook input omits it there). Not a version signal. */
42
+ readonly permissionMode?: PermissionMode;
283
43
  }
284
44
  /**
285
- * The bound a {@link Hooks} seat runs under when the deployment declares none ({@link Hooks.timeoutMs}).
286
- *
287
- * The value is CC's own hook default (600s) rather than a number invented here. It is deliberately
288
- * GENEROUS: the seat bound's job is to make a wedge terminal, not to police how long a legitimate hook
289
- * may think — the task's own abort signal is what ends a hung DECISION seat promptly on a run that has a
290
- * deadline, and this ceiling is what ends it on a run that does not. A deployment whose hooks should
291
- * answer in a second says so with `timeoutMs`.
292
- */
293
- export declare const DEFAULT_HOOK_TIMEOUT_MS = 600000;
294
- /** `setTimeout`'s own ceiling: above it the platform silently clamps to ~1ms, which would turn a
295
- * deployment's "very long bound" into an instant expiry it never asked for. Refused loudly instead
296
- * (same ceiling, same posture as the gate's `approvalTimeoutMs`). */
297
- export declare const MAX_HOOK_TIMEOUT_MS = 2147483647;
298
- /**
299
- * How long an observation seat still gets when the task is cancelled `elapsed` ms into its wait — the
300
- * grace, capped by what is LEFT of the declared deadline.
301
- *
302
- * The `remaining` argument, not the whole deadline, is the correction: arming a fresh
303
- * `min(GRACE, timeoutMs)` from the abort moment restarts the clock, so an abort landing near the
304
- * deadline pushes the seat's absolute end PAST the number the deployment declared — up to double it for
305
- * a sub-second bound — on precisely the cancelled-task settlement path the grace exists to shorten. The
306
- * two contract sentences this file already carries ("never an extension", "a deployment that declared a
307
- * SHORTER `timeoutMs` keeps its own number") were true only in the aborted-on-entry case, where elapsed
308
- * is zero and the two formulas coincide. Now they are true everywhere.
309
- *
310
- * Clamped at zero: a deadline already spent is no grace at all, not a negative timer.
45
+ * `classic.PreToolUse`'s `e` — raised before a tool executes (the gate's phase 1, the approved-edit re-screen, the
46
+ * resumed leg's re-screen of an approved call). Pinned: `tool`, `tool_use_id`, `agentId`, `input` (what a handler
47
+ * judges is what runs — a rewrite is the answer's `updatedInput`). Answer: {@link PreToolUseResult}; a handler with no
48
+ * opinion returns `next(e)`. A handler's `allow` does NOT circumvent the policy: the policy still runs on the (possibly
49
+ * rewritten) args and has the final say ({@link runToolGate}).
311
50
  */
312
- export declare function postAbortGraceMs(remainingMs: number): number;
51
+ export interface PreToolUseEvent extends ClassicEventBase {
52
+ /** The call's wire name (the tool the call dispatches to). */
53
+ readonly tool: string;
54
+ readonly tool_use_id: string;
55
+ /** A deep-frozen copy of the args the gate judges. */
56
+ readonly input: unknown;
57
+ /** The TASK's tracked working directory at this call — the base the hand tools resolve a RELATIVE path against,
58
+ * read live off the same ref the tool policy reads. NOT {@link HookEnvCapabilities.cwd} (the environment's own,
59
+ * which never moves on a shell `cd` or a worktree entry). ABSENT when the run has no tracked cwd to report (no real
60
+ * write hands, a host-driven gate without one, the delegation-fold twin) — absence means "this engine cannot tell
61
+ * you", never "the root". */
62
+ readonly cwd?: string;
63
+ /** The read-only path-resolution face over the env the hands run against ({@link HookEnvCapabilities}). ABSENT when
64
+ * the deployment wired no execution environment at all. */
65
+ readonly env?: HookEnvCapabilities;
66
+ }
67
+ /** `classic.PostToolUse`'s `e` — after a tool executed successfully. Answer: {@link PostToolUseResult}. */
68
+ export interface PostToolUseEvent extends ClassicEventBase {
69
+ readonly tool: string;
70
+ readonly tool_use_id: string;
71
+ readonly input: unknown;
72
+ readonly output: HookToolOutput;
73
+ readonly cwd?: string;
74
+ readonly env?: HookEnvCapabilities;
75
+ }
313
76
  /**
314
- * Read {@link Hooks.timeoutMs} as a usable deadline: the declared value when it is one, the default
315
- * when it is absent, and the default LOUDLY when it is garbage (bad-value loudness — a knob's bad value
316
- * must never resolve silently to the default).
317
- *
318
- * `owner` is the record the value was read off; passing it bounds the disclosure to once per record
319
- * (see {@link badHookTimeoutReported}). Omit it where the caller already bounds its own channel.
77
+ * `classic.PostToolUseFailure`'s `e` — after a tool EXECUTION failed (mutually exclusive with `classic.PostToolUse`).
78
+ * Raised ONLY for genuine execution failures: a gate/policy block, a plan-mode deny, a pre-execution abort or a durable
79
+ * suspend raises neither post event. Answer: {@link PostToolUseFailureResult} (context only; the failure itself cannot
80
+ * be rewritten).
320
81
  */
321
- export declare function resolveHookTimeoutMs(supplied: number | undefined, report?: (err: unknown) => void, owner?: object): number;
322
- /** The ten {@link Hooks} callback seats, named so a bound wait can say WHICH one did not answer. */
323
- export type HookSeatName = "preToolUse" | "postToolUse" | "postToolUseFailure" | "postToolBatch" | "userPromptSubmit" | "stop" | "stopFailure" | "preCompact" | "postCompact" | "permissionDenied"
324
- /** design/380 O2 — not a {@link Hooks} record seat: the `RunnerDeps.resumePreflight` deployment
325
- * callback, run through the same bounded-wait home so the pre-CAS resume ladder cannot grow its
326
- * own dialect of "a deployment callback gets a deadline". */
327
- | "resumePreflight";
328
- /** What one bounded seat invocation produced: the seat's own value, or the fact that the wait ended
329
- * first. A seat that THREW is neither — the throw propagates out of {@link runHookSeat} unchanged, so
330
- * each site's existing crash arm keeps owning it. */
331
- export type HookSeatOutcome<T> = {
332
- readonly expired: false;
333
- readonly value: T;
334
- } | {
335
- readonly expired: true;
336
- readonly cause: "timeout" | "aborted";
337
- };
338
- /** How one seat invocation is bounded. */
339
- export interface HookSeatBound {
340
- /** The deployment's declared bound ({@link Hooks.timeoutMs}); re-validated here, so a caller that
341
- * already validated pays nothing and a caller that did not cannot pass garbage through. */
342
- timeoutMs?: number;
343
- /** The task abort signal. It is ALWAYS forwarded to the seat (so an implementation can cancel its own
344
- * underlying wait) and it ALWAYS shortens the wait — the two seat kinds differ only in HOW MUCH.
345
- * Supply it wherever the caller has one: a seat with no signal is bounded by its deadline alone, and
346
- * a ten-minute default deadline is not a cancellation story. */
347
- signal?: AbortSignal;
348
- /** True for a seat whose answer is MOOT once the task is ending — the DECISION seats. Those settle as
349
- * `aborted` the instant the task is cancelled. An observation seat leaves this false on purpose:
350
- * `postToolUseFailure` fires precisely BECAUSE the task aborted (`isInterrupt`), and cancelling it
351
- * outright would delete the observation the seat exists to deliver — it gets
352
- * {@link POST_ABORT_GRACE_MS} instead of the full deadline. */
353
- abortEnds?: boolean;
354
- /** Where a bad {@link timeoutMs} is disclosed. The EXPIRY itself is not reported here — each site
355
- * words its own, because the sentence a deployment needs includes what the engine then did. */
356
- onBadTimeout?: (err: unknown) => void;
357
- /** The hook record the bound was read off, for once-per-record bad-value disclosure. */
358
- owner?: object;
82
+ export interface PostToolUseFailureEvent extends ClassicEventBase {
83
+ readonly tool: string;
84
+ readonly tool_use_id: string;
85
+ readonly input: unknown;
86
+ readonly failure: HookToolFailure;
87
+ readonly cwd?: string;
88
+ readonly env?: HookEnvCapabilities;
359
89
  }
360
90
  /**
361
- * Run ONE {@link Hooks} seat invocation under its bound — the single home of "a deployment callback
362
- * gets a deadline", so the ten seats cannot grow ten dialects of one wait.
363
- *
364
- * Shape notes, each one load-bearing:
365
- * · the seat is invoked SYNCHRONOUSLY, in this turn, exactly where the bare `await` used to be — a
366
- * callback that reads its own mutable state before its first `await` must see the turn it always saw;
367
- * · a SYNCHRONOUS throw propagates unchanged (it never reached a deadline, and the site's crash arm is
368
- * the contract for it), and so does an asynchronous rejection;
369
- * · the seat's signal fires when — and ONLY when — this function stops waiting, and its promise is then
370
- * abandoned with a rejection sink attached (an abandoned seat that fails later must not surface as an
371
- * unhandled rejection somewhere else). The task's own signal is never forwarded raw: for an
372
- * OBSERVATION seat the task being cancelled is not a reason to stop, it is the reason the seat is
373
- * firing at all;
374
- * · the TASK signal ends the wait on both kinds of seat, differently. A DECISION seat
375
- * ({@link HookSeatBound.abortEnds}) settles as `aborted` the moment the task is cancelled, INCLUDING
376
- * when it was already cancelled on entry — a verdict about a call that will not run decides nothing,
377
- * and waiting the full deadline for one holds a cancelled task open for as long as the deadline says.
378
- * On the entry case the callback is still INVOKED (an audit face keeps seeing the attempt) and its
379
- * answer is discarded: settling on the cancellation rather than on whichever of the two happened to
380
- * land first is what makes the arm a rule instead of a race.
381
- * An OBSERVATION seat is not cancelled by the abort — it fires precisely BECAUSE the task aborted
382
- * (`isInterrupt`), and racing it against that same abort would delete the observation it exists to
383
- * deliver — but it does not get the full deadline either: the abort collapses its wait to
384
- * {@link POST_ABORT_GRACE_MS}. Prompt observers still land; a hung one can no longer hold a cancelled
385
- * task's settlement for the length of the whole bound.
91
+ * `classic.PostToolBatch`'s `e` — raised ONCE after every tool call of one assistant turn resolved, before the next
92
+ * model request (the per-call post events fire first). A zero-tool turn, an aborted or steered-away boundary raise
93
+ * nothing. On a boundary where the engine itself injected (`injectedThisTurn`) the batch is DELIVERED with that
94
+ * attribution and the answer's `additionalContext` is IGNORED (no stacking onto an engine injection). Answer:
95
+ * {@link PostToolBatchResult}.
386
96
  */
387
- export declare function runHookSeat<T>(seat: HookSeatName, bound: HookSeatBound, call: (signal: AbortSignal) => T | Promise<T>): Promise<HookSeatOutcome<T>>;
97
+ export interface PostToolBatchEvent extends ClassicEventBase {
98
+ readonly batch: readonly PostToolBatchCall[];
99
+ readonly injectedThisTurn?: "final_verification" | "finalize";
100
+ }
388
101
  /**
389
- * The ONE wording home for "the engine stopped waiting for a hook seat", so the ten sites cannot drift
390
- * into ten sentences about one fact. `consequence` is the half only the site knows: what the engine did
391
- * instead.
102
+ * `classic.UserPromptSubmit`'s `e` — EVERY user-lane entrance: the objective, a live steer, a wake resume's message
103
+ * (pre-claim, at the verb) and a parked steer's redelivery, discriminated by {@link source}. Pinned: `source`,
104
+ * `inputId`, `actor`. A handler that blocks unconditionally therefore refuses steers too — branch on `source` (and
105
+ * `actor`). Answer: {@link UserPromptSubmitResult}. Block per entrance: objective ⇒ the task ends `blocked`; live steer
106
+ * ⇒ typed `steering.blocked_by_hook`; resume message ⇒ typed pre-claim refusal (the checkpoint stays pending); parked
107
+ * redelivery ⇒ the frame is withheld (a `steering.parked_input_blocked` notice) and the resume proceeds.
392
108
  *
393
- * The CAUSE is named, not glossed. A deadline that elapsed and a task that was cancelled are different
394
- * facts to whoever reads this — the first says a hook is too slow or stuck and wants fixing, the second
395
- * says nothing about the hook at all — and one sentence covering both would have to be false about one
396
- * of them. (It was: the first version of this said "did not answer within its Nms bound" on every
397
- * expiry, including the ones where the bound was never reached.)
109
+ * ⚠️ RE-ENTRANT within one leg: the steer verb goes live before the objective screen runs, so screens can overlap in
110
+ * flight; only steer-vs-steer is serialized (FIFO — each queued steer waits behind its predecessor's whole screen).
398
111
  */
399
- export declare function hookSeatExpiredError(seat: HookSeatName, timeoutMs: number, cause: "timeout" | "aborted", consequence: string): Error;
400
- /** The payload a {@link Hooks.permissionDenied} callback observes (CC-exact fields + the gate outcome). */
401
- export interface PermissionDeniedPayload {
402
- /** #281 件A — the run/leg identity envelope. Present on every ENGINE emission (the gate's single
403
- * deny exit, the hook-crash intercept, the plan-mode and compliance denies); absent only when a
404
- * host drives {@link runToolGate} without `ToolGateInput.identity`. */
405
- identity?: HookInvocationIdentity;
406
- toolName: string;
407
- /** The FINAL (post-hook-rewrite / post-policy-rewrite) args the chain adjudicated — what would have
408
- * executed; not necessarily the model's original args. */
409
- input: unknown;
410
- toolCallId: string;
411
- /** Human/model-readable deny reason (CC `reason`) — the RAW decision text, NOT `<system-reminder>`-wrapped. */
412
- reason: string;
112
+ export interface UserPromptSubmitEvent extends ClassicEventBase {
113
+ /** The caller's text, raw. */
114
+ readonly prompt: string;
115
+ /**
116
+ * WHICH entrance: `"objective"` (the task's opening prompt), `"steer"` (a live mid-run steer), `"resume_message"`
117
+ * (a wake resume's message, screened pre-claim), `"parked_redelivery"` (a parked steer re-screened on the CURRENT
118
+ * process when any resume kind that drains parked steers redelivers it). ⚠️ DOUBLE ARRIVAL IS CONTRACTUAL: one input
119
+ * can arrive twice (screened live as `"steer"`, parked, re-screened as `"parked_redelivery"`) — idempotency is the
120
+ * handler's, keyed on {@link inputId}. `"followUp"` is a RESERVED spelling: no entrance carries it yet.
121
+ */
122
+ readonly source: "objective" | "steer" | "resume_message" | "followUp" | "parked_redelivery";
413
123
  /**
414
- * The gate outcome this deny is — the SAME object the call's `tool_end` frame carries as `gate` and
415
- * the engine's per-call sideband records (one mint, three faces). `disposition.deniedBy` says which
416
- * LAYER refused (our `decision_reason_type` analog for the refusing half); when the refusal came through
417
- * an ask resolution, `settlement` says how the wait ended and `origin` who asked. Nothing is read out of
418
- * an absence: a direct policy / hook / plan-mode / compliance deny carries no settlement because it
419
- * settled none.
124
+ * The screened input's correlation id — present on every entrance but the objective (no id exists there; nothing is
125
+ * fabricated): the steer's effective id, the parked entry's stored id, or the wake message's id (the caller's, else
126
+ * minted PER CALL — a retried wake with no caller id re-screens under a fresh id; a host that retries wakes mints its
127
+ * own). The dedup key for the double-arrival contract.
420
128
  */
421
- gate: GateOutcome;
422
- /** {@link HookToolContext.permissionMode}, read when the deny is observed. */
423
- permissionMode?: PermissionMode;
424
- /** {@link HookSeatSignal} — this invocation's own abort signal. On an OBSERVATION seat the deny has
425
- * already happened and nothing this callback does can change it, so the signal says exactly one
426
- * thing: stop reading, nobody is waiting for your answer any more. */
427
- signal?: AbortSignal;
129
+ readonly inputId?: string;
130
+ /** The API-boundary actor snapshot attributed to this input (the one the `human_input` event reports), when the caller
131
+ * supplied one. Attribution only, never authority. */
132
+ readonly actor?: ActorAssertion;
133
+ /**
134
+ * The files declared on this objective (`TaskSpec.userAttachments`), in order — present only on `source:"objective"`
135
+ * when the objective declares any. A path entry shows its path; a host entry its name and byte count, never its
136
+ * bytes. Blocking the objective reads and delivers none of them. A handler does not govern the engine's reading of an
137
+ * allowed attachment (`classic.PreToolUse` is not raised for it either): a deployment that must keep a path from the
138
+ * model writes a deny rule, which the attachment read honors like the Read tool does.
139
+ */
140
+ readonly userAttachments?: readonly import("./user-attachment.js").UserAttachmentDeclaration[];
428
141
  }
429
- /** Context for {@link Hooks.stopFailure} — aligned with the TaskResult error face (observe-only). */
430
- export interface StopFailureContext {
431
- /** #281 件A — the run/leg identity envelope (always present on the engine's emission; the seat is
432
- * optional only because the envelope's whole contract is). See {@link HookInvocationIdentity}. */
433
- identity?: HookInvocationIdentity;
434
- /** Human-readable error message (the assembled `errorMessage`; any `[code]` prefix already stripped). */
435
- error: string;
436
- /** Machine-readable kind — the assembled `errorCode` lifted from the brain's `[code]` prefix
437
- * (auth / network / rate_limit / server_error / …); absent when the provider stamped no code. */
438
- errorKind?: string;
439
- /** Turns completed when the failure ended the run. */
440
- turns: number;
441
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
442
- signal?: AbortSignal;
142
+ /**
143
+ * `classic.Stop`'s `e` — raised when the agent WOULD OTHERWISE END its run (no more tool calls, steering and follow-up
144
+ * queues dry). Answer: {@link StopHookResult}; `{ block }` pushes back (the reason is injected and the run continues one
145
+ * more turn). Guardrails (CC-exact): consecutive push-backs are capped (default 8 — the loop then overrides and ends with
146
+ * a warning); `stopHookActive` is true while the run is already continuing because of one. `maxTurns` outranks it.
147
+ */
148
+ export interface StopEvent extends ClassicEventBase {
149
+ /** True when this run is already continuing because a previous answer pushed back — check it and let the run end
150
+ * once your condition can't be improved, or it loops to the cap. */
151
+ readonly stopHookActive: boolean;
152
+ /** Consecutive push-backs so far in this run (resets when the run is let proceed). */
153
+ readonly consecutiveBlocks: number;
154
+ /** A LAZY view of the conversation being judged (a handler that only reads the counters pays nothing). Absent when
155
+ * the runner cannot supply one. */
156
+ readonly getBranch?: () => Promise<readonly SessionTreeEntry[]>;
157
+ /** The run's background tasks still in flight (pending / running / parked under this run's owner triple; CC
158
+ * `SubagentStop.background_tasks`). LAZY like {@link getBranch}; `[]` = none, ABSENT = no view. */
159
+ readonly backgroundTasks?: () => ReadonlyArray<{
160
+ id: string;
161
+ description?: string;
162
+ status: string;
163
+ }>;
443
164
  }
444
165
  /**
445
- * The abort signal every hook seat's context carries (`ctx.signal`), documented once here and
446
- * referenced from each seat.
447
- *
448
- * It carries exactly one meaning: **the engine has stopped waiting for this invocation.** It is not the
449
- * task's abort signal and it never fires early on the task's behalf — a DECISION seat's wait ends when
450
- * the task is cancelled, so its signal fires then; an OBSERVATION seat's wait ends at its deadline or
451
- * after the post-abort grace, so its signal fires there and NOT at the cancellation itself. The
452
- * difference is deliberate: a `postToolUseFailure` fires precisely BECAUSE the task aborted, and a
453
- * signal that went off at the cancellation would tell a cooperative observer to abandon the very
454
- * observation it was invoked to deliver.
455
- *
456
- * Honoring it is optional and it changes nothing about the engine's own behavior — the engine stops
457
- * waiting either way. What it is for is the deployment's side of that: a seat that was awaiting a
458
- * network read, a lock or a subprocess can cancel it instead of leaving an orphan running with nobody
459
- * left to read its answer. Absent when a host drives a seat outside the engine's own invocation (no
460
- * bound, nothing to signal).
166
+ * `classic.StopFailure`'s `e` — raised ONCE, after the result is assembled, when the task terminates `failed` BECAUSE
167
+ * the model/API layer errored. NOT for engine-owned failure classes (budget, timeout/abort, max-turns, degenerate /
168
+ * walltime cutoffs, storage conflict, blocked, a durable suspend). Observe-only.
461
169
  */
462
- export type HookSeatSignal = AbortSignal;
170
+ export interface StopFailureEvent extends ClassicEventBase {
171
+ /** Human-readable error message (the assembled `errorMessage`; any `[code]` prefix already stripped). */
172
+ readonly error: string;
173
+ /** Machine-readable kind — the assembled `errorCode` (auth / network / rate_limit / server_error / …); absent when
174
+ * the provider stamped no code. */
175
+ readonly errorKind?: string;
176
+ /** Turns completed when the failure ended the run. */
177
+ readonly turns: number;
178
+ }
179
+ /** `classic.PreCompact`'s `e` — before each compaction, after the trigger gate and a valid cut point. Answer:
180
+ * {@link PreCompactResult} (`block` honored on "auto" / "manual" only). */
181
+ export interface PreCompactEvent extends ClassicEventBase {
182
+ readonly trigger: PreCompactContext["trigger"];
183
+ readonly customInstructions?: string;
184
+ }
185
+ /** `classic.PostCompact`'s `e` — observe-only, after the compaction landed. */
186
+ export interface PostCompactEvent extends ClassicEventBase {
187
+ readonly trigger: PostCompactContext["trigger"];
188
+ readonly summary: string;
189
+ readonly tokensBefore?: number;
190
+ readonly tokensAfter?: number;
191
+ readonly freedTokens?: number;
192
+ }
193
+ /** `classic.PermissionDenied`'s `e` (CC-exact fields + the gate outcome) — raised when the gate's adjudicate chain
194
+ * DENY-SHORT-CIRCUITS a call (a policy deny, a tighten whose ask resolved to deny, the plan-mode write-deny, the
195
+ * compliance deny) and on the engine's fail-closed refusal of a failed PreToolUse screening (`source:"hook"`); NOT on a
196
+ * PreToolUse handler's own deny (that is the handler's decision, and this event attributes the ENGINE's) nor on a
197
+ * durable suspend. Observe-only. `identity` is present on every ENGINE emission; absent only when a host drives
198
+ * {@link runToolGate} without `ToolGateInput.identity`. (CC raises its PermissionDenied from the auto-mode classifier's
199
+ * refusals alone; sema's trigger surface is deliberately WIDER — a divergence, not a parity claim.) */
200
+ export interface PermissionDeniedPayload extends ClassicEventBase {
201
+ readonly toolName: string;
202
+ /** The FINAL (post-rewrite) args the chain adjudicated — what would have executed; not necessarily the model's
203
+ * original args. A detached copy (the live args are shared with later frames and audit records). */
204
+ readonly input: unknown;
205
+ readonly toolCallId: string;
206
+ /** Human/model-readable deny reason (CC `reason`) — the RAW decision text, NOT `<system-reminder>`-wrapped. */
207
+ readonly reason: string;
208
+ /**
209
+ * The gate outcome this deny is — the SAME object the call's `tool_end` frame carries as `gate` and the engine's
210
+ * per-call sideband records (one mint, three faces). `disposition.deniedBy` says which LAYER refused; when the
211
+ * refusal came through an ask resolution, `settlement` says how the wait ended and `origin` who asked.
212
+ */
213
+ readonly gate: GateOutcome;
214
+ }
463
215
  /** The failed tool execution a PostToolUseFailure callback inspects (design/134). */
464
216
  export interface HookToolFailure {
465
217
  /** Model-facing error text of the failed call (the error tool result's text content, joined). */
@@ -497,19 +249,13 @@ export interface PostToolBatchCall {
497
249
  export interface PostToolBatchResult {
498
250
  additionalContext?: string;
499
251
  }
500
- /** Context for {@link Hooks.preCompact} (design/134). */
252
+ /** What the compaction module hands its `preCompact` option (design/134); the Runner raises `classic.PreCompact` from it. */
501
253
  export interface PreCompactContext {
502
- /** #281 件A — the run/leg identity envelope. Present when the Runner drives the compaction (its
503
- * wrapper injects it); honestly absent when a deployment drives the compaction module directly
504
- * (presence law arm 3 on {@link HookInvocationIdentity}). */
505
- identity?: HookInvocationIdentity;
506
254
  /** "auto" = threshold-triggered; "manual" = /compact; "forced" = promptTooLong recovery or
507
255
  * trim-pressure propagation (block is ignored on forced — the compaction is not optional). */
508
256
  trigger: "auto" | "manual" | "forced";
509
257
  /** The summarization instructions in effect (spec/deployment-level), when set. */
510
258
  customInstructions?: string;
511
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
512
- signal?: AbortSignal;
513
259
  }
514
260
  /** A PreCompact result: skip this compaction (auto/manual only) and/or extend the summary instructions. */
515
261
  export interface PreCompactResult {
@@ -521,10 +267,8 @@ export interface PreCompactResult {
521
267
  * extending the provider contract is out of scope for design/134). */
522
268
  additionalInstructions?: string;
523
269
  }
524
- /** Context for {@link Hooks.postCompact} (design/134, observe-only). */
270
+ /** What the compaction module hands its `postCompact` option (design/134, observe-only); the Runner raises `classic.PostCompact` from it. */
525
271
  export interface PostCompactContext {
526
- /** #281 件A — same presence law as {@link PreCompactContext.identity}. */
527
- identity?: HookInvocationIdentity;
528
272
  trigger: "auto" | "manual" | "forced";
529
273
  /** The conversation summary the compaction produced (CC `compact_summary` parity). */
530
274
  summary: string;
@@ -537,45 +281,6 @@ export interface PostCompactContext {
537
281
  /** The engine's measured delta (clamped ≥ 0) — the ONE subtractable number here, and the same value
538
282
  * this pass's `compacted` frame carries. Absent ⇒ the post measurement failed. */
539
283
  freedTokens?: number;
540
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
541
- signal?: AbortSignal;
542
- }
543
- /** Context for the {@link Hooks.stop} hook (CC `stop_hook_active` parity). */
544
- export interface StopHookContext {
545
- /** #281 件A — the run/leg identity envelope. The load-bearing member for THIS seat: a process-level
546
- * `deps.hooks.stop` fires in root, delegated-child and resume legs alike, and previously could not
547
- * tell which run it was being asked to end (matrix row 10/13 residual). Always present on the
548
- * engine's stop-gate emission. */
549
- identity?: HookInvocationIdentity;
550
- /** True when this run is already continuing because a previous stop() blocked — check it and
551
- * return success (undefined) once your condition can't be improved, or you will loop to the cap. */
552
- stopHookActive: boolean;
553
- /** Consecutive blocks so far in this run (resets when a stop() call lets the run proceed). */
554
- consecutiveBlocks: number;
555
- /**
556
- * A view of the conversation this hook is being asked to judge — the counters alone cannot serve a
557
- * hook that asks anything ABOUT THE RUN (the prompt-shaped hooks a settings file declares).
558
- *
559
- * LAZY on purpose: a hook that only reads the counters pays nothing, and a hook that needs the
560
- * conversation gets it in full rather than through a truncation policy the engine would have to invent
561
- * on its behalf. Callers decide what to send to a model and how much — that decision has a budget and a
562
- * trust tier attached, and both belong to the caller.
563
- *
564
- * Absent when the runner cannot supply one (a stop gate wired without a session).
565
- */
566
- getBranch?: () => Promise<readonly SessionTreeEntry[]>;
567
- /** The run's background tasks still in flight (pending / running / parked under this run's owner triple — the
568
- * post-compaction reminder's projection; CC `SubagentStop.background_tasks`). LAZY like {@link getBranch};
569
- * `[]` = none in that projection, ABSENT = no view. A parked child here waits on an approval out of band. */
570
- backgroundTasks?: () => ReadonlyArray<{
571
- id: string;
572
- description?: string;
573
- status: string;
574
- }>;
575
- /** {@link HookToolContext.permissionMode}. */
576
- permissionMode?: PermissionMode;
577
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
578
- signal?: AbortSignal;
579
284
  }
580
285
  /** A Stop hook result: block the run from ending, with a model-readable reason. */
581
286
  export interface StopHookResult {
@@ -602,7 +307,7 @@ export interface StopHookResult {
602
307
  }
603
308
  /**
604
309
  * A READ-ONLY path-resolution capability face over the task's {@link ExecutionEnv}, handed to tool hooks
605
- * on {@link HookToolContext.env} (the hook-env capability seam).
310
+ * on {@link PreToolUseEvent.env} (and the two post events' `env`).
606
311
  *
607
312
  * **Why it exists.** A hook that judges a path — "is this write inside the workspace", "does this
608
313
  * innocent-looking name resolve onto a guarded file" — must resolve it in the filesystem the HANDS
@@ -613,7 +318,7 @@ export interface StopHookResult {
613
318
  * env off the CALL CONTEXT removes the ordering problem structurally: the context is built after the env
614
319
  * is, and a resumed/revived run's rebuilt env is picked up with no re-instantiation protocol.
615
320
  *
616
- * **Capabilities, not the object.** Hooks are an extension boundary the deployment fills with code whose
321
+ * **Capabilities, not the object.** A classic handler is an extension boundary the deployment fills with code whose
617
322
  * job is to DECIDE; the whole `ExecutionEnv` is a face that can ACT (`exec`, `writeFile`, `remove`,
618
323
  * `cleanup`). So this is four INDEPENDENTLY BOUND read primitives and nothing else: no `env`
619
324
  * back-reference, no prototype of the env object, and the face itself is frozen (a hook cannot re-point a
@@ -687,38 +392,26 @@ export interface HookEnvCapabilities {
687
392
  */
688
393
  export declare function createHookEnvCapabilities(env: ExecutionEnv): HookEnvCapabilities;
689
394
  /**
690
- * #281 件A — the UNIFIED IDENTITY ENVELOPE every hook invocation can carry (`ctx.identity`): which
691
- * run, which leg, and where in the delegation tree the hook is firing. The CC analog is
692
- * `createBaseHookInput`'s per-event base fields; the members here are sema's OWN identity facts
693
- * (session/leg/delegation axes), not a transcription of CC's field names.
395
+ * The UNIFIED IDENTITY ENVELOPE every classic event can carry (`e.identity`): which run, which leg, and
396
+ * where in the delegation tree the event is raised. The CC analog is `createBaseHookInput`'s per-event base fields;
397
+ * the members here are sema's OWN identity facts (session/leg/delegation axes), not a transcription of CC's names.
694
398
  *
695
- * WHY: `deps.hooks` is PROCESS-level — one `stop`/`preToolUse`/`permissionDenied` seat serves the
696
- * root run, every delegated child and every resume leg at once, and before this envelope none of
697
- * those invocations could say which leg they belonged to. Additive: every seat is
698
- * `identity?: HookInvocationIdentity`, so a consumer that never reads it sees nothing new.
399
+ * WHY: the deployment seat (`RunnerDeps.mods`) is PROCESS-level — one handler serves the root run, every delegated
400
+ * child and every resume leg at once, and without this envelope none of those events could say which leg it belongs
401
+ * to (`e.agentId` names a delegated leg on every event too).
699
402
  *
700
- * PRESENCE LAW (which invocations carry it — stated here once, referenced per seat):
701
- * · PRESENT on every ENGINE-driven invocation of every hook seat: the tool gate's PreToolUse
702
- * screenings (phase 1 + the approval-edit re-screen), postToolUse / postToolUseFailure,
703
- * postToolBatch, userPromptSubmit, stop, stopFailure, preCompact / postCompact (the Runner's
704
- * wrapper injects it), and every engine `permissionDenied` emission (gate deny exit, hook-crash
705
- * intercept, plan-mode deny, compliance deny). The engine mints ONE frozen envelope per prepared
706
- * leg ({@link mintHookInvocationIdentity} — single mint home) and every station reuses it.
707
- * · HONESTLY ABSENT (no fabrication) at exactly four places, each structurally unable to know:
708
- * 1. a HOST driving the exported {@link runToolGate} directly without supplying
709
- * `ToolGateInput.identity` — the engine cannot know a leg it is not running;
710
- * 2. an inherited PreToolUse screening face riding a DESCENDANT's constraint fold
711
- * ({@link createPreToolUseConstraintPolicy}) — the fold consumes it as a `ToolPolicy`, whose
712
- * request shape carries no leg identity, and stamping the INSTALL-site leg would name the
713
- * wrong run;
714
- * 3. a deployment driving the compaction module directly with its own `preCompact`/`postCompact`
715
- * options — those contexts are built below the runner, which is where the envelope lives;
716
- * 4. the PRE-CAS durable-resume observation of a declared-observational PreToolUse face
717
- * (resumeStream's edited-args leg) — it runs BEFORE the resume leg prepares, so the leg's
718
- * envelope does not exist yet, and minting a partial one off the checkpoint would guess at
719
- * the delegation axes (the row persists `isDelegatedChild` alone; `insideFork`/`agentName`/
720
- * `parentToolCallId` are internals-only, and a wrong guess is worse than an honest
721
- * absence). The SAME face is re-consulted with full identity by the resumed leg's own gate.
403
+ * PRESENCE LAW:
404
+ * · PRESENT on every ENGINE-raised classic event: the gate's `classic.PreToolUse` screenings (phase 1, the
405
+ * approval-edit re-screen, the resumed leg's re-screen), the two post events, `classic.PostToolBatch`, every
406
+ * `classic.UserPromptSubmit` entrance (the resume-message screen carries the pre-leg envelope minted off the
407
+ * checkpoint and the trusted spawn facts), `classic.Stop`, `classic.StopFailure`, the compaction pair, and every
408
+ * engine `classic.PermissionDenied`. The engine mints ONE frozen envelope per prepared leg
409
+ * ({@link mintHookInvocationIdentity} — single mint home) and every station reuses it.
410
+ * · HONESTLY ABSENT (no fabrication) where the engine structurally cannot know:
411
+ * 1. a HOST driving the exported {@link runToolGate} directly without supplying `ToolGateInput.identity`;
412
+ * 2. an ancestor's chain riding a DESCENDANT's constraint fold ({@link createPreToolUseConstraintPolicy}) — the
413
+ * fold's request carries no leg identity, and stamping the INSTALL-site leg would name the wrong run;
414
+ * 3. a deployment driving the compaction module directly with its own `preCompact`/`postCompact` options.
722
415
  *
723
416
  * CONSUMER NOTE — the minted object is FROZEN and carries a NULL PROTOTYPE (same rule and same
724
417
  * trade as {@link HookEnvCapabilities}: absence must be a fact about this object, not about what
@@ -776,55 +469,6 @@ export interface HookInvocationIdentity {
776
469
  * is the only member whose value a model influences.
777
470
  */
778
471
  export declare function mintHookInvocationIdentity(facts: HookInvocationIdentity): HookInvocationIdentity;
779
- /** Identifying context passed to tool hooks. */
780
- export interface HookToolContext {
781
- toolCallId: string;
782
- toolName: string;
783
- /** #281 件A — the run/leg identity envelope; see {@link HookInvocationIdentity} for the presence
784
- * law (present on engine-driven gates; absent on a host-driven gate without
785
- * `ToolGateInput.identity` and on the delegation-fold twin). */
786
- identity?: HookInvocationIdentity;
787
- /**
788
- * The read-only path-resolution face over the env the hands run against (see
789
- * {@link HookEnvCapabilities} for the why, and for why it is capabilities rather than the env object).
790
- *
791
- * ABSENT when the deployment wired no execution environment at all (no `executionEnv` /
792
- * `executionEnvFactory`: the run has no hands, and the engine's internal no-I/O placeholder is not an
793
- * environment to report). Present ⇒ inspect the individual members: each one is itself optional.
794
- */
795
- env?: HookEnvCapabilities;
796
- /**
797
- * The TASK's tracked working directory at this call — the base the hand tools will resolve a RELATIVE
798
- * path against, read live off the same ref the tool policy reads (so a hook and a policy judging the
799
- * same call judge it against the same base).
800
- *
801
- * It is NOT {@link HookEnvCapabilities.cwd}, and the difference is the reason this member exists: that
802
- * one is the ENVIRONMENT's own working directory, which never moves when the shell `cd`s or the run
803
- * enters a worktree. A hook is documented — on the capability face itself — as the seat that judges
804
- * "is this write inside the workspace", and it was the only seat in the gate that could not resolve a
805
- * relative target the way the tool would. CC ships its own `cwd` to every hook event for the same
806
- * reason; this is that field, named for the value it actually carries.
807
- *
808
- * ABSENT when the run has no tracked cwd to report — no real write hands (read-only or no shell), a
809
- * host driving {@link runToolGate} without one, or the delegation-fold twin (which runs inside a
810
- * DESCENDANT's policy fold and would otherwise report the install site's directory, i.e. the wrong
811
- * machine's answer). Absence means "this engine cannot tell you", never "the root".
812
- */
813
- cwd?: string;
814
- /** The leg's EFFECTIVE permission mode when the callback is invoked — the mode seat's `observed` read (it arms
815
- * no classifier on the leg or an ancestor, so it moves no decision); a steer, a plan-mode entry or exit and a
816
- * resumed leg's restored word show on the next callback. Stamped by the Runner on its own legs' contexts (this
817
- * one, {@link PermissionDeniedPayload}, {@link UserPromptSubmitContext}, {@link PostToolBatchContext},
818
- * {@link StopHookContext}); ABSENT ⇔ no leg seat: a host-driven {@link runToolGate} (its
819
- * `ToolGateInput.permissionMode` is the arbitration's reader, never called for this), the delegation-fold twin,
820
- * the `"resume_message"` screen (before the resumed leg exists). Not a version signal. The compaction seats and
821
- * {@link StopFailureContext} carry none (the reference's hook input omits it there). */
822
- permissionMode?: PermissionMode;
823
- /** {@link HookSeatSignal} — this invocation's own abort signal. A FRESH one per screening pass, like
824
- * the context object it rides: phase 1 and the approval-edit re-screen are two bounded waits, and a
825
- * shared signal would let the first pass's expiry cancel the second. */
826
- signal?: AbortSignal;
827
- }
828
472
  /** The executed tool result a PostToolUse hook inspects. */
829
473
  export interface HookToolOutput {
830
474
  content: Array<TextContent | ImageContent | DocumentContent>;
@@ -861,7 +505,7 @@ export interface HookToolOutput {
861
505
  * {@link createPreToolUseConstraintPolicy} speaks the same words as a policy layer). "Held" = the ask survives to a person:
862
506
  * neither an allow rule (`decisionReason: "hook"` is outside the allow layer) nor the classifier (hook asks are
863
507
  * never classifier-answerable) clears it.
864
- * · returns `allow` (± `updatedInput` / `additionalContext`) → the face's own word (`hookAllowed`); the policy
508
+ * · returns `allow` (± `updatedInput` / `additionalContext`) → the face's own word (`hookAllowedBy`, naming the registration); the policy
865
509
  * still adjudicates the FINAL args; the allow layer's hook member may clear an unmandated ask. Not held.
866
510
  * · returns `ask` (± `updatedInput`, ± `requiresRealApproval`) → folded as `hookAsk`: a policy deny still
867
511
  * outranks it, a policy allow is PROMOTED to this ask with `"hook"` stamped by the engine; the rewrite rides
@@ -869,10 +513,10 @@ export interface HookToolOutput {
869
513
  * · returns `deny` → {@link hookDenyExit}: the hook's OWN refusal — the deny observer does NOT fire, the model
870
514
  * reads the hook's sentence. Nothing executes.
871
515
  * · returns `undefined` ("no opinion") or no screening face is installed → no word at all; the policy decides.
872
- * · throws / times out / is aborted / returns an out-of-contract object (refused by {@link receiveHookResult}) →
873
- * {@link engineFailClosedExit}: an ENGINE deny — the deny observer fires, a crash and a timeout also reach
874
- * `onHookError`→`onError(phase:"hook")` (an abort does not), the model reads the engine's envelope. Nothing
875
- * executes, nothing is held.
516
+ * · a handler throws / runs past its budget / breaks the site's contract (an out-of-contract answer, refused by
517
+ * {@link screenPreToolUseResult}), or the dispatch is aborted → {@link engineFailClosedExit}: an ENGINE deny — the
518
+ * deny observer fires, a crash and a timeout also reach `onError(phase:"hook")` (an abort does not), the model reads
519
+ * the engine's envelope. Nothing executes, nothing is held.
876
520
  */
877
521
  export type PreToolUseResult = PermissionResult & {
878
522
  additionalContext?: string;
@@ -884,80 +528,6 @@ export interface PostToolUseResult {
884
528
  /** Appended to the result as a `<system-reminder>` the model can read. */
885
529
  additionalContext?: string;
886
530
  }
887
- /** #281 件A — the context of a {@link Hooks.userPromptSubmit} invocation (second parameter,
888
- * additive). An object so later additions stay additive; design/373 §4.3 (D2) grew the
889
- * entrance-discrimination trio below. */
890
- export interface UserPromptSubmitContext {
891
- /** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
892
- identity?: HookInvocationIdentity;
893
- /** {@link HookToolContext.permissionMode} (absent on `"resume_message"`, screened before the leg exists). */
894
- permissionMode?: PermissionMode;
895
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
896
- signal?: AbortSignal;
897
- /**
898
- * design/373 §4.3 (D2) — WHICH user-lane entrance this text is entering through. The screen now
899
- * sits at every user-lane entrance, so a hook that used to see only the objective can (and, if it
900
- * blocks unconditionally, MUST) branch on this: `"objective"` = the task's opening prompt (the
901
- * pre-373 only caller); `"steer"` = a live mid-run TaskStream.steer; `"resume_message"` = the
902
- * message riding a wake resume (screened at the verb, pre-CAS); `"parked_redelivery"` = a parked
903
- * steer being re-screened on the CURRENT process's hook when a resume redelivers it — ANY resume
904
- * kind that drains parked steers (policy_ask / dry_run_review / plan_review / resource_limit,
905
- * wake included), because a park's screening cannot be carried across processes (hooks are
906
- * process-local config). ⚠️ DOUBLE ARRIVAL IS CONTRACTUAL:
907
- * the same input can reach a hook twice with different sources (screened live as `"steer"`,
908
- * parked, re-screened as `"parked_redelivery"`); idempotency is the hook's to hold — a stateful
909
- * hook keys on {@link inputId}. `"followUp"` is a RESERVED spelling (spec seat only): the Runner
910
- * face has no user-lane followUp verb today, so no invocation carries it yet — if/when that face
911
- * lands it arrives screened under this name (never a silent widening).
912
- * Absent ⇒ an emission from a pre-373 engine (old engines stamped nothing); every current engine
913
- * seat stamps its name, so treat absence as "version unknown", never as a sixth entrance.
914
- */
915
- source?: "objective" | "steer" | "resume_message" | "followUp" | "parked_redelivery";
916
- /**
917
- * design/373 §4.3 (D2) — the screened input's correlation id. Present on EVERY entrance except
918
- * the objective seat (no id exists on that face — nothing is fabricated there):
919
- * - `"steer"` — the effective `inputId` (caller-supplied or engine-minted — the SAME id the
920
- * `human_input` event and any parked copy carry);
921
- * - `"parked_redelivery"` — the parked entry's STORED id (stable across redeliveries);
922
- * - `"resume_message"` — always present too: the caller's id verbatim when it supplied one,
923
- * otherwise MINTED PER CALL by the wake validator. ⚠️ Per call, not per message: a retried wake
924
- * that supplies no caller id and REACHES this seat re-screens identical text under a FRESH id.
925
- * A dedup/quota hook keying its ledger here gets cross-attempt stability only when the CALLER
926
- * supplies the id — a host that retries wakes should mint its own. What the engine guarantees
927
- * instead (#472) is that the seat is not reached needlessly: it sits BELOW every retryable
928
- * pre-CAS refusal on the resume ladder, so an attempt the engine was going to refuse anyway
929
- * (a full governance window most of all) costs ZERO invocations — the retry loop that refusal
930
- * invites cannot bill this hook per attempt.
931
- * This is the dedup key for the double-arrival contract on {@link source}: a quota-charging hook
932
- * that must not double-charge one input keys its ledger here.
933
- */
934
- inputId?: string;
935
- /**
936
- * design/373 §4.3 (D2) — the API-boundary actor snapshot attributed to this input (the same
937
- * snapshot the `human_input` event reports), when the caller supplied one. Attribution only,
938
- * never authority — a hook wanting to exempt its own supervisor lane branches on this PLUS
939
- * {@link source}, and absence means "anonymous caller", exactly as on the event face.
940
- */
941
- actor?: ActorAssertion;
942
- /**
943
- * The files declared on this objective (`TaskSpec.userAttachments`), in order — present only on `source:"objective"`
944
- * when the objective declares any. A path entry shows its path; a host entry its name and byte count, never its
945
- * bytes. Blocking the objective reads and delivers none of them. A hook does not govern the engine's reading of an
946
- * allowed attachment (PreToolUse does not run for it either): a deployment that must keep a path from the model
947
- * writes a deny rule, which the attachment read honors like the Read tool does.
948
- */
949
- userAttachments?: readonly import("./user-attachment.js").UserAttachmentDeclaration[];
950
- }
951
- /** #281 件A — the context of a {@link Hooks.postToolBatch} invocation (third parameter, additive —
952
- * deliberately NOT folded into `meta`, whose presence already means "the engine injected here"). */
953
- export interface PostToolBatchContext {
954
- /** The run/leg identity envelope — see {@link HookInvocationIdentity} for the presence law. */
955
- identity?: HookInvocationIdentity;
956
- /** {@link HookToolContext.permissionMode}. */
957
- permissionMode?: PermissionMode;
958
- /** {@link HookSeatSignal} — this invocation's own abort signal. */
959
- signal?: AbortSignal;
960
- }
961
531
  /** A UserPromptSubmit hook result: block the submission, or inject context ahead of the prompt. */
962
532
  export interface UserPromptSubmitResult {
963
533
  /** Block submission entirely; the task fails with this model-readable reason. */
@@ -980,7 +550,9 @@ export interface ToolGateResult {
980
550
  * frame, and the deny observer receives the same object: one mint, three faces.
981
551
  */
982
552
  gate?: GateOutcome;
983
- /** Rewritten args to execute with (re-validated by the loop); omitted when nothing rewrote. */
553
+ /** The args to execute with — the pass's own immutable copy, the value every judge of the call read (the model's args
554
+ * as taken at the gate's entry, or what a hook, a policy or a person rewrote them to); re-validated by the loop. Present
555
+ * on every allow exit, whether or not anything rewrote (compare values to tell); absent on a block and a suspend. */
984
556
  updatedInput?: unknown;
985
557
  /** The input this pass decided on (what a refusal refused, what an allowed call runs with) as a deep, FROZEN copy taken
986
558
  * at the exit — never the live args. Beside {@link gate} on both terminal exits; absent on the suspend exit. The deny
@@ -1020,18 +592,8 @@ export interface ToolGateResult {
1020
592
  */
1021
593
  export type ContentAskOutcome = {
1022
594
  kind: "answered";
1023
- /** The EXACT batch that was put in front of the person, retained by the resolver as an isolated
1024
- * snapshot. The gate executes this and nothing else, so "what was shown" and "what runs" are the
1025
- * same object graph by construction rather than by two parties agreeing not to mutate one. */
1026
- presentedInput: unknown;
1027
595
  } | {
1028
596
  kind: "unavailable";
1029
- /** The retained snapshot, when the resolver got far enough to make one (i.e. it actually waited
1030
- * on a callback). The park persists THIS, not the caller's live object: the wait is a window in
1031
- * which whoever owns the rewrite can change it, and a checkpoint holding bytes the gate never
1032
- * adjudicated is one a later human would answer as a different question. Absent when no wait
1033
- * happened, in which case there is no window and the live object is still the validated one. */
1034
- presentedInput?: unknown;
1035
597
  /** True iff the park closure took its SYNC-FIRST DECLINE for this call — it has not yet made a
1036
598
  * real suspend attempt, so re-entering it now IS that attempt rather than a second one. False
1037
599
  * when the park already had its attempt (no live face to prefer, or a durable mandate): the gate
@@ -1042,11 +604,6 @@ export type ContentAskOutcome = {
1042
604
  } | {
1043
605
  kind: "delivery_failure";
1044
606
  code: string;
1045
- /** The retained snapshot, when the resolver got far enough to make one. Executed for the same
1046
- * reason the answered arm's is: the bound failure names the batch it belongs to, and executing a
1047
- * different one would drop it and re-ask the person. Absent when the refusal happened before a
1048
- * snapshot could be taken. */
1049
- presentedInput?: unknown;
1050
607
  };
1051
608
  /**
1052
609
  * A durable park that was ATTEMPTED for this call and could not be minted, carrying the reason in the
@@ -1176,7 +733,7 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1176
733
  toolName: string;
1177
734
  input: Record<string, unknown>;
1178
735
  };
1179
- /** #281 件A — the run/leg identity envelope, put verbatim on every {@link HookToolContext} this
736
+ /** The run/leg identity envelope, put verbatim on every {@link PreToolUseEvent} this
1180
737
  * gate call builds and on every {@link PermissionDeniedPayload} it emits. The Runner supplies its
1181
738
  * per-leg mint; a host driving the gate directly may omit it, and the contexts then carry no
1182
739
  * identity (honest absence — this layer never fabricates a leg). */
@@ -1185,30 +742,31 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1185
742
  * reasons ride `formatHookFeedback`, so their open tags must carry the same mark the run's
1186
743
  * system-prompt declaration names. Absent (a host driving the gate directly) ⇒ bare tags. */
1187
744
  reminderMark?: string;
1188
- preToolUse?: Hooks["preToolUse"];
1189
745
  /**
1190
- * The bound the {@link preToolUse} screenings run under — {@link Hooks.timeoutMs}, carried in from
1191
- * the hook record the caller resolved it off (the gate receives the callback, not the record). Absent
1192
- * ⇒ {@link DEFAULT_HOOK_TIMEOUT_MS}. A garbage value is refused loudly to that default via
1193
- * {@link onHookError}, exactly as {@link approvalTimeoutMs} is.
1194
- *
1195
- * On elapse the screening FAILS CLOSED — the call does not execute — and the block reason names the
1196
- * hook and the bound. That direction is not a new posture for this seat: a hook that THROWS is
1197
- * already the fail-closed deny here, and a hook that never answers has told the gate strictly less
1198
- * than one that crashed. A face the deployment declared observational is not exempt, for the same
1199
- * reason its throw is not: the declaration says its VERDICTS do not count, and a wait that never
1200
- * ends is not a verdict.
746
+ * The leg's mods bus: phase 1, the approved-edit re-screen and (through the Runner) the approved-call re-screen raise
747
+ * `classic.PreToolUse` on it; the lanes raise `tool.check` (once per adjudication, never over a deny). Absent, or no
748
+ * handler on the event ⇒ nothing raised. A handler that throws, runs past its budget or breaks the site's contract refuses
749
+ * the call fail-closed (the event's failure column), and so does a cancel; an observe handler never decides.
1201
750
  */
1202
- hookTimeoutMs?: number;
1203
- /** The task's live tracked cwd reader, put on every {@link HookToolContext} this gate call builds
1204
- * (see {@link HookToolContext.cwd}). A FUNCTION rather than a string for the reason the policy's own
751
+ modBus?: Pick<import("./mods/kernel/index.js").ModBus, "hasHandlers" | "dispatch">;
752
+ /** The inherited installations whose `tool.check` judges this leg's calls too (`inheritedToolChecks`, the rule's one home):
753
+ * raised after {@link modBus}, each over the verdict the previous left standing, read by the one comparison — their `allow`
754
+ * never clears an ask (a constraint only tightens), and their `ask` goes to each installation's own `settle` (where its
755
+ * installation froze the question), never to this gate's resolver; an approval there that edits the call takes this gate's
756
+ * approved-edit pass. Absent ⇒ only {@link modBus} is raised. */
757
+ inheritedToolChecks?: readonly import("./mods/stations.js").InheritedToolCheck[];
758
+ /** The leg's observed permission mode, carried on every `classic.PreToolUse` e this gate call raises. Absent ⇒ the e
759
+ * carries none (a host-driven gate with no leg seat). */
760
+ observedPermissionMode?: () => PermissionMode;
761
+ /** The task's live tracked cwd reader, put on every {@link PreToolUseEvent} this gate call raises
762
+ * (see {@link PreToolUseEvent.cwd}). A FUNCTION rather than a string for the reason the policy's own
1205
763
  * threading gives: capturing the value when the gate's wiring is built would freeze the task's
1206
764
  * starting directory and answer for a run that has since `cd`ed. Absent ⇒ the contexts carry no
1207
765
  * `cwd`, which is the honest answer for a run with no tracked one. */
1208
766
  trackedCwd?: () => string | undefined;
1209
767
  /** The leg's target-reading stamp, applied where the gate's request is minted over the final args (a rewrite re-reads it: `withArgsReading`). */
1210
768
  stampCallTarget?: CallTargetStamp;
1211
- /** The read-only env capability face put on every {@link HookToolContext} this gate call
769
+ /** The read-only env capability face put on every {@link PreToolUseEvent} this gate call
1212
770
  * builds ({@link HookEnvCapabilities}). Built ONCE per task by the runner (after the env is minted) and
1213
771
  * passed through unchanged; omitted when the deployment wired no execution environment. */
1214
772
  hookEnv?: HookEnvCapabilities;
@@ -1383,27 +941,22 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1383
941
  * (timeout/cancel) in addition to {@link approvalTimeoutMs}; an abort while probing fails closed to `ask`. */
1384
942
  abortSignal?: AbortSignal;
1385
943
  /**
1386
- * design/134 R5: observer for the gate's DENY short-circuit ({@link Hooks.permissionDenied}), pre-wrapped
1387
- * by the caller (swallow + onError) so it never throws. Fired ONCE, at the single deny exit of the
944
+ * design/134 R5: a host's own observer for the gate's DENY short-circuit (the Runner's are its leg's handlers on
945
+ * {@link modBus}), run as one more observe handler of `classic.PermissionDenied` — that station's columns; a failure
946
+ * is disclosed on {@link onNotifyError} as `toolGate.permissionDenied`. Fired ONCE, at the single deny exit of the
1388
947
  * adjudicate chain — a policy deny or a resolved-ask deny. NOT fired for a PreToolUse hook deny (the
1389
- * exclusion on {@link Hooks.permissionDenied}, which also records why that exclusion is not a parity
1390
- * claim) or a durable suspend (the ask path). runToolGate still try/catches defensively: an observer
1391
- * must never alter the deny outcome.
948
+ * exclusion on {@link PermissionDeniedPayload}, which also records why that exclusion is not a parity
949
+ * claim) or a durable suspend (the ask path).
1392
950
  *
1393
951
  * ALSO fired for the engine's fail-closed interception of a THROWING PreToolUse hook, with
1394
952
  * `source:"hook"` — the exclusion is the hook's own deny DECISION, not an engine decision.
1395
953
  */
1396
954
  permissionDenied?: (payload: PermissionDeniedPayload) => void | Promise<void>;
1397
955
  /**
1398
- * Trace sink for a PreToolUse hook that THREW — the deployment-facing half of the crash
1399
- * (`onError(phase:"hook")`, wired by the caller). The model-facing half is the fail-closed block reason;
1400
- * this one carries the exception object itself to whoever runs the deployment, because a crashing hook is
1401
- * a bug someone has to fix and the model-facing summary is bounded/sanitized. Never affects the outcome
1402
- * (a throwing sink is swallowed).
1403
- *
1404
- * ALSO fired for a {@link reversibilityProbe} that threw or timed out (same species — a
1405
- * deployment-supplied callback failing while the gate holds the fail-closed line) and for a
1406
- * malformed {@link approvalTimeoutMs} refused to the bounded default. A task-abort rejection
956
+ * Trace sink for a deployment-supplied callback of this gate that failed while the gate holds the fail-closed line
957
+ * — a {@link reversibilityProbe} that threw or timed out — and for a malformed {@link approvalTimeoutMs} refused to
958
+ * the bounded default (`onError(phase:"hook")`, wired by the caller). A failed `classic.PreToolUse` handler is
959
+ * reported by the bus itself. Never affects the outcome (a throwing sink is swallowed). A task-abort rejection
1407
960
  * mid-probe is NOT reported (normal cancellation, not a defect).
1408
961
  */
1409
962
  onHookError?: (err: unknown) => void;
@@ -1733,56 +1286,52 @@ export interface ToolGateInput extends PermissionSeatInputs, SkillGateSeats, All
1733
1286
  };
1734
1287
  }
1735
1288
  /**
1736
- * Fold a {@link Hooks.preToolUse} callback into a {@link ToolPolicy} layer, so a screening face a task
1737
- * installed for ITSELF can also travel down a delegation chain as one more inherited constraint.
1289
+ * The PreToolUse screening face ({@link PreToolUseScreening}) for ONE call — `classic.PreToolUse` raised on the leg's
1290
+ * bus, the per-screening e and the fail-closed sentences, built in ONE place for every site that screens a call: the
1291
+ * gate's phase 1 and approved-edit re-screen (through {@link runToolGate}'s pass) and the resumed leg's re-screen of an
1292
+ * approved call. Undefined when no handler hangs on the event.
1738
1293
  *
1739
- * Why a policy and not a second hook channel: a delegated child already folds each ancestor's caller
1740
- * policy (`InheritedGate.parentConstraints`), and that fold is where `deny > ask > allow`, the rewrite
1741
- * cascade, the ancestor-frozen approver and the durable mandate all already live. Expressing the
1742
- * screening face in the same shape means the child gains no new decision channel — the existing fold
1743
- * consumes it — and the tighten-only property is structural rather than restated.
1294
+ * The e: `tool` (the call's wire name), `tool_use_id`, `input` — a deep-frozen copy of the args, so what a handler
1295
+ * judges is what runs (`input` is pinned; a rewrite is the answer's `updatedInput`) — `agentId` on a delegated leg,
1296
+ * `cwd` (the task's tracked cwd, read per screening), `env` (the capability face), `identity`, `permissionMode`.
1297
+ */
1298
+ export declare function createPreToolUseScreening(input: Pick<ToolGateInput, "event" | "modBus" | "observedPermissionMode" | "trackedCwd" | "hookEnv" | "identity" | "abortSignal">): PreToolUseScreening | undefined;
1299
+ /**
1300
+ * Fold a leg's `classic.PreToolUse` chain into a {@link ToolPolicy} layer, so the screening a task installed can also
1301
+ * travel down a delegation chain as one more inherited constraint — the constraint MINT. `chain` is the installing
1302
+ * leg's bus; the policy raises `classic.PreToolUse` on it with the handlers that JUDGE (an observe handler never enters
1303
+ * a constraint — the participant filter below is that rule's one home).
1744
1304
  *
1745
- * The mapping mirrors the gate's own phase 1 (see {@link runToolGate}) decision for decision:
1746
- * - no opinion (`undefined`) → `allow`, contributing nothing;
1747
- * - `deny` → `deny` in the fold's own words (the fold short-circuits, and the gate's deny exit supplies
1748
- * the same `formatHookFeedback` envelope phase 1 would have);
1749
- * - `ask` → `ask`, stamped `decisionReason:"hook"` so every consumer that distinguishes a screening ask
1750
- * from a policy ask reads the same fact one level down — notably the persisted-rule lane, which must
1751
- * not let a standing allow-rule resolve a screening ask;
1752
- * - `allow`/`ask` with `updatedInput` → the same rewrite, which the fold cascades to later layers and
1753
- * re-checks against the narrowing layers;
1754
- * - a THROW → the fail-closed deny with the RB-442 attribution text (naming the hook and the
1755
- * non-execution), because a screening face that cannot answer must not pass the call.
1305
+ * Why a policy and not a second channel: a delegated child already folds each ancestor's caller policy
1306
+ * (`InheritedGate.parentConstraints`), and that fold is where `deny > ask > allow`, the rewrite cascade, the
1307
+ * ancestor-frozen approver and the durable mandate all already live. Expressing the screening in the same shape means
1308
+ * the child gains no new decision channel — the existing fold consumes it — and the tighten-only property is
1309
+ * structural rather than restated.
1756
1310
  *
1757
- * **The callback inherits the fold's idempotency requirement** ({@link combinePolicies}): a layer's
1758
- * `check` may run more than once for a single tool call — the fold scans past an `ask` looking for a
1759
- * later `deny`, a rewrite emitted by a LATER layer re-runs the prefix that never saw the final args, and
1760
- * an approved operator edit is re-adjudicated. A callback whose consultation has a side effect (an audit
1761
- * row, a counter) will therefore see that call more than once on those paths. Its own gate's phase 1 has
1762
- * always had the second of these (an approved edit is re-screened there too).
1311
+ * The mapping mirrors the gate's own phase 1 decision for decision:
1312
+ * - no opinion → `allow`, contributing nothing;
1313
+ * - `deny` → `deny` in the fold's own words;
1314
+ * - `ask` → `ask`, stamped `decisionReason:"hook"` (and the mandate, when the answer set it) so every consumer that
1315
+ * distinguishes a screening ask from a policy ask reads the same fact one level down;
1316
+ * - `allow`/`ask` with `updatedInput` → the same rewrite, which the fold cascades and re-checks;
1317
+ * - a failed screening (a handler that threw, ran past its budget or broke the site's contract) or a cancel → the
1318
+ * fail-closed deny, because a screening that cannot answer must not pass the call. The installing leg's bus
1319
+ * already reported a crash or a timeout on that leg's error lane.
1763
1320
  *
1764
- * Two properties are deliberately NOT carried, and neither is reconstructible in this shape:
1765
- * - `additionalContext` — a policy verdict has no channel for model-facing text. Context a screening
1766
- * face injects is a property of the run whose gate ran it; a descendant's transcript does not receive
1767
- * it. (The decision half — the part that restricts — is what travels.)
1768
- * - the phase-1 observer exclusion — a deny raised here is a fold deny, so the descendant's
1769
- * `permissionDenied` observer sees it with `source:"policy"`. That exclusion is about a hook deciding
1770
- * in ITS OWN gate; here the descendant's policy fold is what refused the call.
1321
+ * **Idempotency** ({@link combinePolicies}): a layer's `check` may run more than once for a single tool call (the fold
1322
+ * scans past an `ask`, a later rewrite re-runs the prefix, an approved edit is re-adjudicated), so a handler with a side
1323
+ * effect sees that call more than once on those paths. Its own leg's phase 1 has always had the third of these.
1771
1324
  *
1772
- * `env` is the capability face of the environment the callback was installed against — captured at the
1773
- * install site, not rebuilt in the descendant, for the same reason an inherited policy closure keeps its
1774
- * own captured environment (the descendant may run against a different root). `onCrash` is the install
1775
- * site's deployment error lane: the gate's phase 1 routes a crashing callback to `onError(phase:"hook")`
1776
- * so a broken screening face is distinguishable from a deliberate refusal, and the folded form has to
1777
- * reach the same lane from inside a descendant's fold.
1325
+ * Not carried, and not reconstructible in this shape: `additionalContext` (a policy verdict has no channel for
1326
+ * model-facing text — the context belongs to the run whose gate ran it), the leg identity and the tracked cwd (the
1327
+ * fold's request carries neither, and the installing leg's would name the wrong run), and the phase-1 observer
1328
+ * exclusion (a deny raised here is a fold deny: the descendant's deny observer sees it as `source:"policy"`). `env` is
1329
+ * the capability face of the environment the chain was installed against, captured at the install site.
1330
+ *
1331
+ * The chain travels on the minted policy: a descendant's gate raises the installation's `tool.check` on it (a verdict read is
1332
+ * not a fold layer). A host re-supplying the entry hands back the policy minted here, as it is.
1778
1333
  */
1779
- export declare function createPreToolUseConstraintPolicy(preToolUse: NonNullable<Hooks["preToolUse"]>, env?: HookEnvCapabilities, onCrash?: (err: unknown) => void,
1780
- /** The install site's {@link Hooks.timeoutMs}. The folded form was ALREADY bounded by the descendant's
1781
- * abort race — that asymmetry (bounded as an inherited constraint, unbounded in its own gate) is what
1782
- * named the hole in the first place — but an abort bound is not a deadline: on a descendant with no
1783
- * walltime and no cancel, the race never fires. So the same seat bound rides here too, and the two
1784
- * installations of one callback are now bounded the same way for the same reason. */
1785
- timeoutMs?: number): ToolPolicy;
1334
+ export declare function createPreToolUseConstraintPolicy(chain: Pick<import("./mods/kernel/index.js").ModBus, "hasHandlers" | "dispatch">, env?: HookEnvCapabilities): ToolPolicy;
1786
1335
  /**
1787
1336
  * The park closure's ONE structural seat — what the gate hands the durable park beyond the twelve
1788
1337
  * positional seats. Every member is optional and read by name, so a new thing the park must know
@@ -1831,13 +1380,6 @@ export declare function askCarryRowMembers(carry: AskCarry | undefined): {
1831
1380
  askClass?: import("./ask-origin.js").AskClass;
1832
1381
  classes?: readonly import("./ask-origin.js").AskClass[];
1833
1382
  };
1834
- /**
1835
- * The PreToolUse screening seat ({@link PreToolUseScreening}) for ONE call — seat runner, seat bound, per-screening
1836
- * context and the fail-closed / deny sentences, built in ONE place for every site that screens a call: the gate's
1837
- * phase 1 and approved-edit re-screen (through {@link runToolGate}'s pass) and the resumed leg's re-screen of an
1838
- * approved call. Undefined when no face is wired. `hookSeatMs` is the caller's validated bound.
1839
- */
1840
- export declare function createPreToolUseScreening(preToolUse: Hooks["preToolUse"] | undefined, input: Pick<ToolGateInput, "event" | "trackedCwd" | "hookEnv" | "identity" | "onHookError" | "abortSignal">, hookSeatMs: number, notifier: SafeNotifier): PreToolUseScreening | undefined;
1841
1383
  /**
1842
1384
  * The design/37 **two-phase tool gate** — the single chokepoint that makes the load-bearing invariant
1843
1385
  * structural: a hook's `allow` passes no deny, no ask rule and no mandated question; it is one member of the