apex-code 0.0.1-alpha.8 → 0.0.4

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 (634) hide show
  1. package/CHANGELOG.md +231 -0
  2. package/README.md +9 -8
  3. package/dist/cli/args.d.ts +26 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +72 -10
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/file-processor.d.ts.map +1 -1
  8. package/dist/cli/file-processor.js +2 -1
  9. package/dist/cli/file-processor.js.map +1 -1
  10. package/dist/cli/mcp-command.d.ts +9 -0
  11. package/dist/cli/mcp-command.d.ts.map +1 -0
  12. package/dist/cli/mcp-command.js +53 -0
  13. package/dist/cli/mcp-command.js.map +1 -0
  14. package/dist/cli/project-trust.d.ts.map +1 -1
  15. package/dist/cli/project-trust.js +3 -1
  16. package/dist/cli/project-trust.js.map +1 -1
  17. package/dist/cli/startup-ui.d.ts.map +1 -1
  18. package/dist/cli/startup-ui.js +2 -1
  19. package/dist/cli/startup-ui.js.map +1 -1
  20. package/dist/cli.d.ts.map +1 -1
  21. package/dist/cli.js +94 -5
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config.d.ts +3 -2
  24. package/dist/config.d.ts.map +1 -1
  25. package/dist/config.js +45 -18
  26. package/dist/config.js.map +1 -1
  27. package/dist/core/agent-session-services.d.ts +3 -0
  28. package/dist/core/agent-session-services.d.ts.map +1 -1
  29. package/dist/core/agent-session-services.js +1 -0
  30. package/dist/core/agent-session-services.js.map +1 -1
  31. package/dist/core/agent-session.d.ts +183 -15
  32. package/dist/core/agent-session.d.ts.map +1 -1
  33. package/dist/core/agent-session.js +728 -117
  34. package/dist/core/agent-session.js.map +1 -1
  35. package/dist/core/auth-storage.d.ts.map +1 -1
  36. package/dist/core/auth-storage.js +6 -7
  37. package/dist/core/auth-storage.js.map +1 -1
  38. package/dist/core/bash-executor.d.ts.map +1 -1
  39. package/dist/core/bash-executor.js +7 -0
  40. package/dist/core/bash-executor.js.map +1 -1
  41. package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
  42. package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
  43. package/dist/core/checkpoints/git-checkpoints.js +251 -0
  44. package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
  45. package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
  46. package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
  47. package/dist/core/checkpoints/session-checkpoints.js +36 -0
  48. package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
  49. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  50. package/dist/core/compaction/branch-summarization.js +7 -3
  51. package/dist/core/compaction/branch-summarization.js.map +1 -1
  52. package/dist/core/compaction/compaction.d.ts +9 -3
  53. package/dist/core/compaction/compaction.d.ts.map +1 -1
  54. package/dist/core/compaction/compaction.js +59 -36
  55. package/dist/core/compaction/compaction.js.map +1 -1
  56. package/dist/core/defaults.d.ts +1 -0
  57. package/dist/core/defaults.d.ts.map +1 -1
  58. package/dist/core/defaults.js +9 -0
  59. package/dist/core/defaults.js.map +1 -1
  60. package/dist/core/delegation/runtime.d.ts +6 -5
  61. package/dist/core/delegation/runtime.d.ts.map +1 -1
  62. package/dist/core/delegation/runtime.js +4 -3
  63. package/dist/core/delegation/runtime.js.map +1 -1
  64. package/dist/core/durable-state/sqlite.d.ts.map +1 -1
  65. package/dist/core/durable-state/sqlite.js +26 -2
  66. package/dist/core/durable-state/sqlite.js.map +1 -1
  67. package/dist/core/exec.d.ts.map +1 -1
  68. package/dist/core/exec.js +5 -2
  69. package/dist/core/exec.js.map +1 -1
  70. package/dist/core/extensions/index.d.ts +2 -2
  71. package/dist/core/extensions/index.d.ts.map +1 -1
  72. package/dist/core/extensions/index.js +1 -1
  73. package/dist/core/extensions/index.js.map +1 -1
  74. package/dist/core/extensions/loader.d.ts.map +1 -1
  75. package/dist/core/extensions/loader.js +123 -50
  76. package/dist/core/extensions/loader.js.map +1 -1
  77. package/dist/core/extensions/runner.d.ts +5 -0
  78. package/dist/core/extensions/runner.d.ts.map +1 -1
  79. package/dist/core/extensions/runner.js +45 -1
  80. package/dist/core/extensions/runner.js.map +1 -1
  81. package/dist/core/extensions/source-runtime.d.ts +14 -0
  82. package/dist/core/extensions/source-runtime.d.ts.map +1 -0
  83. package/dist/core/extensions/source-runtime.js +56 -0
  84. package/dist/core/extensions/source-runtime.js.map +1 -0
  85. package/dist/core/extensions/types.d.ts +60 -11
  86. package/dist/core/extensions/types.d.ts.map +1 -1
  87. package/dist/core/extensions/types.js +3 -0
  88. package/dist/core/extensions/types.js.map +1 -1
  89. package/dist/core/footer-data-provider.d.ts +1 -1
  90. package/dist/core/footer-data-provider.d.ts.map +1 -1
  91. package/dist/core/footer-data-provider.js +1 -1
  92. package/dist/core/footer-data-provider.js.map +1 -1
  93. package/dist/core/formatter-lifecycle.d.ts +69 -0
  94. package/dist/core/formatter-lifecycle.d.ts.map +1 -0
  95. package/dist/core/formatter-lifecycle.js +360 -0
  96. package/dist/core/formatter-lifecycle.js.map +1 -0
  97. package/dist/core/hooks/command-handler.d.ts +19 -0
  98. package/dist/core/hooks/command-handler.d.ts.map +1 -0
  99. package/dist/core/hooks/command-handler.js +112 -0
  100. package/dist/core/hooks/command-handler.js.map +1 -0
  101. package/dist/core/hooks/http-handler.d.ts +10 -0
  102. package/dist/core/hooks/http-handler.d.ts.map +1 -0
  103. package/dist/core/hooks/http-handler.js +36 -0
  104. package/dist/core/hooks/http-handler.js.map +1 -0
  105. package/dist/core/hooks/loader.d.ts +29 -0
  106. package/dist/core/hooks/loader.d.ts.map +1 -0
  107. package/dist/core/hooks/loader.js +139 -0
  108. package/dist/core/hooks/loader.js.map +1 -0
  109. package/dist/core/hooks/runtime.d.ts +13 -0
  110. package/dist/core/hooks/runtime.d.ts.map +1 -0
  111. package/dist/core/hooks/runtime.js +63 -0
  112. package/dist/core/hooks/runtime.js.map +1 -0
  113. package/dist/core/hooks/types.d.ts +83 -0
  114. package/dist/core/hooks/types.d.ts.map +1 -0
  115. package/dist/core/hooks/types.js +24 -0
  116. package/dist/core/hooks/types.js.map +1 -0
  117. package/dist/core/keybindings.d.ts +20 -19
  118. package/dist/core/keybindings.d.ts.map +1 -1
  119. package/dist/core/keybindings.js +26 -5
  120. package/dist/core/keybindings.js.map +1 -1
  121. package/dist/core/mcp/config.d.ts +16 -0
  122. package/dist/core/mcp/config.d.ts.map +1 -0
  123. package/dist/core/mcp/config.js +166 -0
  124. package/dist/core/mcp/config.js.map +1 -0
  125. package/dist/core/mcp/connector.d.ts +35 -0
  126. package/dist/core/mcp/connector.d.ts.map +1 -0
  127. package/dist/core/mcp/connector.js +105 -0
  128. package/dist/core/mcp/connector.js.map +1 -0
  129. package/dist/core/mcp/contract.d.ts +19 -0
  130. package/dist/core/mcp/contract.d.ts.map +1 -0
  131. package/dist/core/mcp/contract.js +89 -0
  132. package/dist/core/mcp/contract.js.map +1 -0
  133. package/dist/core/mcp/mcp-tool.d.ts +32 -0
  134. package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
  135. package/dist/core/mcp/mcp-tool.js +171 -0
  136. package/dist/core/mcp/mcp-tool.js.map +1 -0
  137. package/dist/core/mcp/metadata-cache.d.ts +29 -0
  138. package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
  139. package/dist/core/mcp/metadata-cache.js +99 -0
  140. package/dist/core/mcp/metadata-cache.js.map +1 -0
  141. package/dist/core/mcp/oauth/authorize.d.ts +21 -0
  142. package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
  143. package/dist/core/mcp/oauth/authorize.js +32 -0
  144. package/dist/core/mcp/oauth/authorize.js.map +1 -0
  145. package/dist/core/mcp/oauth/discover.d.ts +27 -0
  146. package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
  147. package/dist/core/mcp/oauth/discover.js +116 -0
  148. package/dist/core/mcp/oauth/discover.js.map +1 -0
  149. package/dist/core/mcp/oauth/flow.d.ts +31 -0
  150. package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
  151. package/dist/core/mcp/oauth/flow.js +175 -0
  152. package/dist/core/mcp/oauth/flow.js.map +1 -0
  153. package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
  154. package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
  155. package/dist/core/mcp/oauth/mcp-token.js +129 -0
  156. package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
  157. package/dist/core/mcp/oauth/pkce.d.ts +12 -0
  158. package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
  159. package/dist/core/mcp/oauth/pkce.js +11 -0
  160. package/dist/core/mcp/oauth/pkce.js.map +1 -0
  161. package/dist/core/mcp/oauth/register.d.ts +16 -0
  162. package/dist/core/mcp/oauth/register.d.ts.map +1 -0
  163. package/dist/core/mcp/oauth/register.js +42 -0
  164. package/dist/core/mcp/oauth/register.js.map +1 -0
  165. package/dist/core/mcp/oauth/token-client.d.ts +33 -0
  166. package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
  167. package/dist/core/mcp/oauth/token-client.js +65 -0
  168. package/dist/core/mcp/oauth/token-client.js.map +1 -0
  169. package/dist/core/mcp/runtime.d.ts +20 -0
  170. package/dist/core/mcp/runtime.d.ts.map +1 -0
  171. package/dist/core/mcp/runtime.js +37 -0
  172. package/dist/core/mcp/runtime.js.map +1 -0
  173. package/dist/core/mcp/schema.d.ts +22 -0
  174. package/dist/core/mcp/schema.d.ts.map +1 -0
  175. package/dist/core/mcp/schema.js +20 -0
  176. package/dist/core/mcp/schema.js.map +1 -0
  177. package/dist/core/mcp/server-manager.d.ts +43 -0
  178. package/dist/core/mcp/server-manager.d.ts.map +1 -0
  179. package/dist/core/mcp/server-manager.js +142 -0
  180. package/dist/core/mcp/server-manager.js.map +1 -0
  181. package/dist/core/mcp/types.d.ts +100 -0
  182. package/dist/core/mcp/types.d.ts.map +1 -0
  183. package/dist/core/mcp/types.js +8 -0
  184. package/dist/core/mcp/types.js.map +1 -0
  185. package/dist/core/model-config.d.ts +5 -0
  186. package/dist/core/model-config.d.ts.map +1 -1
  187. package/dist/core/model-config.js +3 -1
  188. package/dist/core/model-config.js.map +1 -1
  189. package/dist/core/model-resolver.d.ts +1 -0
  190. package/dist/core/model-resolver.d.ts.map +1 -1
  191. package/dist/core/model-resolver.js +14 -8
  192. package/dist/core/model-resolver.js.map +1 -1
  193. package/dist/core/models-store.d.ts.map +1 -1
  194. package/dist/core/models-store.js +2 -1
  195. package/dist/core/models-store.js.map +1 -1
  196. package/dist/core/package-manager.d.ts.map +1 -1
  197. package/dist/core/package-manager.js +22 -14
  198. package/dist/core/package-manager.js.map +1 -1
  199. package/dist/core/permissions/gate.d.ts.map +1 -1
  200. package/dist/core/permissions/gate.js +45 -4
  201. package/dist/core/permissions/gate.js.map +1 -1
  202. package/dist/core/permissions/operations.d.ts +20 -0
  203. package/dist/core/permissions/operations.d.ts.map +1 -0
  204. package/dist/core/permissions/operations.js +9 -0
  205. package/dist/core/permissions/operations.js.map +1 -0
  206. package/dist/core/permissions/policy-command.d.ts +42 -0
  207. package/dist/core/permissions/policy-command.d.ts.map +1 -0
  208. package/dist/core/permissions/policy-command.js +44 -0
  209. package/dist/core/permissions/policy-command.js.map +1 -0
  210. package/dist/core/permissions/responder.d.ts +49 -2
  211. package/dist/core/permissions/responder.d.ts.map +1 -1
  212. package/dist/core/permissions/responder.js +20 -8
  213. package/dist/core/permissions/responder.js.map +1 -1
  214. package/dist/core/permissions/rules.d.ts.map +1 -1
  215. package/dist/core/permissions/rules.js +11 -1
  216. package/dist/core/permissions/rules.js.map +1 -1
  217. package/dist/core/permissions/store.d.ts +18 -0
  218. package/dist/core/permissions/store.d.ts.map +1 -1
  219. package/dist/core/permissions/store.js +89 -8
  220. package/dist/core/permissions/store.js.map +1 -1
  221. package/dist/core/pi-manifest.d.ts.map +1 -1
  222. package/dist/core/pi-manifest.js +2 -1
  223. package/dist/core/pi-manifest.js.map +1 -1
  224. package/dist/core/policy-executor.d.ts +47 -0
  225. package/dist/core/policy-executor.d.ts.map +1 -0
  226. package/dist/core/policy-executor.js +228 -0
  227. package/dist/core/policy-executor.js.map +1 -0
  228. package/dist/core/policy-loader.d.ts +72 -0
  229. package/dist/core/policy-loader.d.ts.map +1 -0
  230. package/dist/core/policy-loader.js +195 -0
  231. package/dist/core/policy-loader.js.map +1 -0
  232. package/dist/core/project-trust.d.ts +1 -1
  233. package/dist/core/project-trust.d.ts.map +1 -1
  234. package/dist/core/project-trust.js.map +1 -1
  235. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  236. package/dist/core/remote-catalog-provider.js +2 -1
  237. package/dist/core/remote-catalog-provider.js.map +1 -1
  238. package/dist/core/resource-loader.d.ts.map +1 -1
  239. package/dist/core/resource-loader.js +3 -2
  240. package/dist/core/resource-loader.js.map +1 -1
  241. package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
  242. package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
  243. package/dist/core/sandbox/bwrap-arguments.js +136 -0
  244. package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
  245. package/dist/core/sandbox/child-entry.d.ts.map +1 -1
  246. package/dist/core/sandbox/child-entry.js +3 -1
  247. package/dist/core/sandbox/child-entry.js.map +1 -1
  248. package/dist/core/sandbox/cli-launch.d.ts +84 -4
  249. package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
  250. package/dist/core/sandbox/cli-launch.js +115 -16
  251. package/dist/core/sandbox/cli-launch.js.map +1 -1
  252. package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
  253. package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
  254. package/dist/core/sandbox/cli-supervisor.js +50 -2
  255. package/dist/core/sandbox/cli-supervisor.js.map +1 -1
  256. package/dist/core/sandbox/full-access.d.ts +28 -0
  257. package/dist/core/sandbox/full-access.d.ts.map +1 -0
  258. package/dist/core/sandbox/full-access.js +63 -0
  259. package/dist/core/sandbox/full-access.js.map +1 -0
  260. package/dist/core/sandbox/git-identity.d.ts +57 -0
  261. package/dist/core/sandbox/git-identity.d.ts.map +1 -0
  262. package/dist/core/sandbox/git-identity.js +78 -0
  263. package/dist/core/sandbox/git-identity.js.map +1 -0
  264. package/dist/core/sandbox/host-approval.d.ts +42 -0
  265. package/dist/core/sandbox/host-approval.d.ts.map +1 -0
  266. package/dist/core/sandbox/host-approval.js +99 -0
  267. package/dist/core/sandbox/host-approval.js.map +1 -0
  268. package/dist/core/sandbox/linux-backend.d.ts +14 -0
  269. package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
  270. package/dist/core/sandbox/linux-backend.js +158 -80
  271. package/dist/core/sandbox/linux-backend.js.map +1 -1
  272. package/dist/core/sandbox/macos-backend.d.ts +5 -0
  273. package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
  274. package/dist/core/sandbox/macos-backend.js +164 -5
  275. package/dist/core/sandbox/macos-backend.js.map +1 -1
  276. package/dist/core/sandbox/network-proxy.d.ts +19 -0
  277. package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
  278. package/dist/core/sandbox/network-proxy.js +93 -19
  279. package/dist/core/sandbox/network-proxy.js.map +1 -1
  280. package/dist/core/sandbox/policy.d.ts +8 -0
  281. package/dist/core/sandbox/policy.d.ts.map +1 -1
  282. package/dist/core/sandbox/policy.js +29 -12
  283. package/dist/core/sandbox/policy.js.map +1 -1
  284. package/dist/core/sandbox/profiles.d.ts +25 -0
  285. package/dist/core/sandbox/profiles.d.ts.map +1 -0
  286. package/dist/core/sandbox/profiles.js +23 -0
  287. package/dist/core/sandbox/profiles.js.map +1 -0
  288. package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
  289. package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
  290. package/dist/core/sandbox/rpc/command-client.js +86 -0
  291. package/dist/core/sandbox/rpc/command-client.js.map +1 -0
  292. package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
  293. package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
  294. package/dist/core/sandbox/rpc/command-proxy.js +120 -0
  295. package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
  296. package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
  297. package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
  298. package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
  299. package/dist/core/sandbox/rpc/framing.d.ts +35 -0
  300. package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
  301. package/dist/core/sandbox/rpc/framing.js +114 -0
  302. package/dist/core/sandbox/rpc/framing.js.map +1 -0
  303. package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
  304. package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
  305. package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
  306. package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
  307. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
  308. package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
  309. package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
  310. package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
  311. package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
  312. package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
  313. package/dist/core/sandbox/supervisor-temp.js +21 -0
  314. package/dist/core/sandbox/supervisor-temp.js.map +1 -0
  315. package/dist/core/sandbox/supervisor.d.ts +12 -1
  316. package/dist/core/sandbox/supervisor.d.ts.map +1 -1
  317. package/dist/core/sandbox/supervisor.js +15 -1
  318. package/dist/core/sandbox/supervisor.js.map +1 -1
  319. package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
  320. package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
  321. package/dist/core/sandbox/terminal-handoff.js +221 -0
  322. package/dist/core/sandbox/terminal-handoff.js.map +1 -0
  323. package/dist/core/sandbox/terminal-size.d.ts +13 -0
  324. package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
  325. package/dist/core/sandbox/terminal-size.js +23 -2
  326. package/dist/core/sandbox/terminal-size.js.map +1 -1
  327. package/dist/core/sdk.d.ts +24 -2
  328. package/dist/core/sdk.d.ts.map +1 -1
  329. package/dist/core/sdk.js +63 -4
  330. package/dist/core/sdk.js.map +1 -1
  331. package/dist/core/session-export.d.ts +4 -0
  332. package/dist/core/session-export.d.ts.map +1 -0
  333. package/dist/core/session-export.js +32 -0
  334. package/dist/core/session-export.js.map +1 -0
  335. package/dist/core/session-manager.d.ts +13 -1
  336. package/dist/core/session-manager.d.ts.map +1 -1
  337. package/dist/core/session-manager.js +34 -6
  338. package/dist/core/session-manager.js.map +1 -1
  339. package/dist/core/settings-diagnostics.d.ts +9 -0
  340. package/dist/core/settings-diagnostics.d.ts.map +1 -0
  341. package/dist/core/settings-diagnostics.js +21 -0
  342. package/dist/core/settings-diagnostics.js.map +1 -0
  343. package/dist/core/settings-manager.d.ts +172 -1
  344. package/dist/core/settings-manager.d.ts.map +1 -1
  345. package/dist/core/settings-manager.js +160 -11
  346. package/dist/core/settings-manager.js.map +1 -1
  347. package/dist/core/skills.d.ts.map +1 -1
  348. package/dist/core/skills.js +51 -33
  349. package/dist/core/skills.js.map +1 -1
  350. package/dist/core/slash-commands.d.ts.map +1 -1
  351. package/dist/core/slash-commands.js +4 -1
  352. package/dist/core/slash-commands.js.map +1 -1
  353. package/dist/core/system-prompt.d.ts.map +1 -1
  354. package/dist/core/system-prompt.js +8 -2
  355. package/dist/core/system-prompt.js.map +1 -1
  356. package/dist/core/tools/background-shell.d.ts +39 -0
  357. package/dist/core/tools/background-shell.d.ts.map +1 -0
  358. package/dist/core/tools/background-shell.js +81 -0
  359. package/dist/core/tools/background-shell.js.map +1 -0
  360. package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
  361. package/dist/core/tools/bash-command-segments.js +2 -0
  362. package/dist/core/tools/bash-command-segments.js.map +1 -1
  363. package/dist/core/tools/bash.d.ts +44 -16
  364. package/dist/core/tools/bash.d.ts.map +1 -1
  365. package/dist/core/tools/bash.js +264 -32
  366. package/dist/core/tools/bash.js.map +1 -1
  367. package/dist/core/tools/contract-snapshot.d.ts +58 -0
  368. package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
  369. package/dist/core/tools/contract-snapshot.js +51 -0
  370. package/dist/core/tools/contract-snapshot.js.map +1 -0
  371. package/dist/core/tools/contract.d.ts +53 -5
  372. package/dist/core/tools/contract.d.ts.map +1 -1
  373. package/dist/core/tools/contract.js +22 -2
  374. package/dist/core/tools/contract.js.map +1 -1
  375. package/dist/core/tools/delegate.d.ts +6 -4
  376. package/dist/core/tools/delegate.d.ts.map +1 -1
  377. package/dist/core/tools/delegate.js +21 -11
  378. package/dist/core/tools/delegate.js.map +1 -1
  379. package/dist/core/tools/edit-diff.d.ts +48 -5
  380. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  381. package/dist/core/tools/edit-diff.js +217 -8
  382. package/dist/core/tools/edit-diff.js.map +1 -1
  383. package/dist/core/tools/edit.d.ts +8 -0
  384. package/dist/core/tools/edit.d.ts.map +1 -1
  385. package/dist/core/tools/edit.js +72 -10
  386. package/dist/core/tools/edit.js.map +1 -1
  387. package/dist/core/tools/find.d.ts.map +1 -1
  388. package/dist/core/tools/find.js +5 -2
  389. package/dist/core/tools/find.js.map +1 -1
  390. package/dist/core/tools/grep.d.ts.map +1 -1
  391. package/dist/core/tools/grep.js +5 -2
  392. package/dist/core/tools/grep.js.map +1 -1
  393. package/dist/core/tools/index.d.ts +14 -2
  394. package/dist/core/tools/index.d.ts.map +1 -1
  395. package/dist/core/tools/index.js +12 -0
  396. package/dist/core/tools/index.js.map +1 -1
  397. package/dist/core/tools/ls.d.ts.map +1 -1
  398. package/dist/core/tools/ls.js +5 -2
  399. package/dist/core/tools/ls.js.map +1 -1
  400. package/dist/core/tools/lsp.d.ts +19 -1
  401. package/dist/core/tools/lsp.d.ts.map +1 -1
  402. package/dist/core/tools/lsp.js +163 -10
  403. package/dist/core/tools/lsp.js.map +1 -1
  404. package/dist/core/tools/path-permission.d.ts +3 -0
  405. package/dist/core/tools/path-permission.d.ts.map +1 -1
  406. package/dist/core/tools/path-permission.js +16 -4
  407. package/dist/core/tools/path-permission.js.map +1 -1
  408. package/dist/core/tools/path-utils.d.ts +27 -2
  409. package/dist/core/tools/path-utils.d.ts.map +1 -1
  410. package/dist/core/tools/path-utils.js +221 -4
  411. package/dist/core/tools/path-utils.js.map +1 -1
  412. package/dist/core/tools/powershell.d.ts +16 -0
  413. package/dist/core/tools/powershell.d.ts.map +1 -0
  414. package/dist/core/tools/powershell.js +39 -0
  415. package/dist/core/tools/powershell.js.map +1 -0
  416. package/dist/core/tools/read.d.ts.map +1 -1
  417. package/dist/core/tools/read.js +20 -11
  418. package/dist/core/tools/read.js.map +1 -1
  419. package/dist/core/tools/test.d.ts +54 -9
  420. package/dist/core/tools/test.d.ts.map +1 -1
  421. package/dist/core/tools/test.js +239 -19
  422. package/dist/core/tools/test.js.map +1 -1
  423. package/dist/core/tools/write.d.ts +7 -0
  424. package/dist/core/tools/write.d.ts.map +1 -1
  425. package/dist/core/tools/write.js +64 -9
  426. package/dist/core/tools/write.js.map +1 -1
  427. package/dist/core/trust-manager.d.ts.map +1 -1
  428. package/dist/core/trust-manager.js +2 -1
  429. package/dist/core/trust-manager.js.map +1 -1
  430. package/dist/core/verification-lifecycle.d.ts +77 -0
  431. package/dist/core/verification-lifecycle.d.ts.map +1 -0
  432. package/dist/core/verification-lifecycle.js +146 -0
  433. package/dist/core/verification-lifecycle.js.map +1 -0
  434. package/dist/core/workspace/artifacts.d.ts +45 -0
  435. package/dist/core/workspace/artifacts.d.ts.map +1 -0
  436. package/dist/core/workspace/artifacts.js +165 -0
  437. package/dist/core/workspace/artifacts.js.map +1 -0
  438. package/dist/core/workspace/comparison.d.ts +27 -0
  439. package/dist/core/workspace/comparison.d.ts.map +1 -0
  440. package/dist/core/workspace/comparison.js +94 -0
  441. package/dist/core/workspace/comparison.js.map +1 -0
  442. package/dist/core/workspace/git-observer.d.ts +48 -0
  443. package/dist/core/workspace/git-observer.d.ts.map +1 -0
  444. package/dist/core/workspace/git-observer.js +346 -0
  445. package/dist/core/workspace/git-observer.js.map +1 -0
  446. package/dist/core/workspace/projection.d.ts +3 -0
  447. package/dist/core/workspace/projection.d.ts.map +1 -0
  448. package/dist/core/workspace/projection.js +56 -0
  449. package/dist/core/workspace/projection.js.map +1 -0
  450. package/dist/core/workspace/state.d.ts +133 -0
  451. package/dist/core/workspace/state.d.ts.map +1 -0
  452. package/dist/core/workspace/state.js +96 -0
  453. package/dist/core/workspace/state.js.map +1 -0
  454. package/dist/extensions/llama/client.d.ts +6 -0
  455. package/dist/extensions/llama/client.d.ts.map +1 -1
  456. package/dist/extensions/llama/client.js +7 -0
  457. package/dist/extensions/llama/client.js.map +1 -1
  458. package/dist/extensions/llama/index.d.ts.map +1 -1
  459. package/dist/extensions/llama/index.js +2 -0
  460. package/dist/extensions/llama/index.js.map +1 -1
  461. package/dist/extensions/llama/provider.d.ts +3 -1
  462. package/dist/extensions/llama/provider.d.ts.map +1 -1
  463. package/dist/extensions/llama/provider.js +29 -4
  464. package/dist/extensions/llama/provider.js.map +1 -1
  465. package/dist/index.d.ts +8 -5
  466. package/dist/index.d.ts.map +1 -1
  467. package/dist/index.js +7 -4
  468. package/dist/index.js.map +1 -1
  469. package/dist/main.d.ts +6 -0
  470. package/dist/main.d.ts.map +1 -1
  471. package/dist/main.js +58 -23
  472. package/dist/main.js.map +1 -1
  473. package/dist/migrations.d.ts.map +1 -1
  474. package/dist/migrations.js +4 -3
  475. package/dist/migrations.js.map +1 -1
  476. package/dist/modes/acp/server.d.ts +55 -0
  477. package/dist/modes/acp/server.d.ts.map +1 -0
  478. package/dist/modes/acp/server.js +244 -0
  479. package/dist/modes/acp/server.js.map +1 -0
  480. package/dist/modes/acp/translate.d.ts +37 -0
  481. package/dist/modes/acp/translate.d.ts.map +1 -0
  482. package/dist/modes/acp/translate.js +50 -0
  483. package/dist/modes/acp/translate.js.map +1 -0
  484. package/dist/modes/index.d.ts +1 -0
  485. package/dist/modes/index.d.ts.map +1 -1
  486. package/dist/modes/index.js +1 -0
  487. package/dist/modes/index.js.map +1 -1
  488. package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
  489. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  490. package/dist/modes/interactive/components/assistant-message.js +182 -23
  491. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  492. package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
  493. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  494. package/dist/modes/interactive/components/bash-execution.js +20 -0
  495. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  496. package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
  497. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  498. package/dist/modes/interactive/components/custom-editor.js +20 -9
  499. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  500. package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
  501. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  502. package/dist/modes/interactive/components/extension-selector.js +7 -3
  503. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  504. package/dist/modes/interactive/components/footer.d.ts +19 -0
  505. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  506. package/dist/modes/interactive/components/footer.js +70 -10
  507. package/dist/modes/interactive/components/footer.js.map +1 -1
  508. package/dist/modes/interactive/components/model-selector.d.ts +10 -3
  509. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  510. package/dist/modes/interactive/components/model-selector.js +56 -14
  511. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  512. package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
  513. package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
  514. package/dist/modes/interactive/components/permission-preview.js +33 -0
  515. package/dist/modes/interactive/components/permission-preview.js.map +1 -0
  516. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  517. package/dist/modes/interactive/components/session-selector.js +2 -2
  518. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  519. package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
  520. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  521. package/dist/modes/interactive/components/settings-selector.js +128 -56
  522. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  523. package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
  524. package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
  525. package/dist/modes/interactive/components/settings-submenu.js +165 -0
  526. package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
  527. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  528. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  529. package/dist/modes/interactive/components/thinking-selector.js +74 -17
  530. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  531. package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
  532. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  533. package/dist/modes/interactive/components/tool-execution.js +64 -10
  534. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  535. package/dist/modes/interactive/external-editor.d.ts.map +1 -1
  536. package/dist/modes/interactive/external-editor.js +2 -1
  537. package/dist/modes/interactive/external-editor.js.map +1 -1
  538. package/dist/modes/interactive/interactive-mode.d.ts +45 -0
  539. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  540. package/dist/modes/interactive/interactive-mode.js +395 -43
  541. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  542. package/dist/modes/interactive/theme/theme.d.ts +12 -0
  543. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  544. package/dist/modes/interactive/theme/theme.js +20 -9
  545. package/dist/modes/interactive/theme/theme.js.map +1 -1
  546. package/dist/modes/json-event.d.ts +16 -13
  547. package/dist/modes/json-event.d.ts.map +1 -1
  548. package/dist/modes/json-event.js +20 -6
  549. package/dist/modes/json-event.js.map +1 -1
  550. package/dist/modes/print-mode.d.ts.map +1 -1
  551. package/dist/modes/print-mode.js +27 -12
  552. package/dist/modes/print-mode.js.map +1 -1
  553. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  554. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  555. package/dist/modes/rpc/rpc-client.js +7 -0
  556. package/dist/modes/rpc/rpc-client.js.map +1 -1
  557. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  558. package/dist/modes/rpc/rpc-mode.js +3 -0
  559. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  560. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  561. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  562. package/dist/modes/rpc/rpc-types.js.map +1 -1
  563. package/dist/package-manager-cli.d.ts +1 -0
  564. package/dist/package-manager-cli.d.ts.map +1 -1
  565. package/dist/package-manager-cli.js +196 -10
  566. package/dist/package-manager-cli.js.map +1 -1
  567. package/dist/utils/frontmatter.d.ts.map +1 -1
  568. package/dist/utils/frontmatter.js +2 -1
  569. package/dist/utils/frontmatter.js.map +1 -1
  570. package/dist/utils/management-http.d.ts +5 -3
  571. package/dist/utils/management-http.d.ts.map +1 -1
  572. package/dist/utils/management-http.js +14 -10
  573. package/dist/utils/management-http.js.map +1 -1
  574. package/dist/utils/shell.d.ts +3 -0
  575. package/dist/utils/shell.d.ts.map +1 -1
  576. package/dist/utils/shell.js +23 -9
  577. package/dist/utils/shell.js.map +1 -1
  578. package/dist/utils/syntax-highlight.d.ts +1 -0
  579. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  580. package/dist/utils/syntax-highlight.js +60 -1
  581. package/dist/utils/syntax-highlight.js.map +1 -1
  582. package/dist/utils/text.d.ts +8 -0
  583. package/dist/utils/text.d.ts.map +1 -0
  584. package/dist/utils/text.js +9 -0
  585. package/dist/utils/text.js.map +1 -0
  586. package/dist/utils/version-check.d.ts.map +1 -1
  587. package/dist/utils/version-check.js +6 -4
  588. package/dist/utils/version-check.js.map +1 -1
  589. package/docs/compaction.md +19 -0
  590. package/docs/custom-provider.md +12 -9
  591. package/docs/environment-variables.md +1 -1
  592. package/docs/extensions.md +35 -7
  593. package/docs/json.md +7 -2
  594. package/docs/keybindings.md +9 -9
  595. package/docs/llama-cpp.md +2 -0
  596. package/docs/models.md +8 -2
  597. package/docs/packages.md +2 -2
  598. package/docs/quickstart.md +1 -1
  599. package/docs/rpc.md +32 -3
  600. package/docs/sdk.md +32 -2
  601. package/docs/settings.md +23 -7
  602. package/docs/skills.md +4 -3
  603. package/docs/terminal-setup.md +23 -7
  604. package/docs/usage.md +10 -5
  605. package/docs/windows.md +24 -2
  606. package/examples/extensions/border-status-editor.ts +1 -1
  607. package/examples/extensions/built-in-tool-renderer.ts +8 -4
  608. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  609. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  610. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  611. package/examples/extensions/git-checkpoint.ts +24 -36
  612. package/examples/extensions/gondolin/package-lock.json +2 -2
  613. package/examples/extensions/gondolin/package.json +1 -1
  614. package/examples/extensions/minimal-mode.ts +2 -2
  615. package/examples/extensions/notify.ts +3 -1
  616. package/examples/extensions/permission-gate.ts +1 -1
  617. package/examples/extensions/plan-mode/index.ts +1 -1
  618. package/examples/extensions/rpc-demo.ts +1 -1
  619. package/examples/extensions/sandbox/package-lock.json +2 -2
  620. package/examples/extensions/sandbox/package.json +1 -1
  621. package/examples/extensions/subagent/README.md +1 -1
  622. package/examples/extensions/subagent/index.ts +6 -1
  623. package/examples/extensions/titlebar-spinner.ts +1 -1
  624. package/examples/extensions/with-deps/package-lock.json +2 -2
  625. package/examples/extensions/with-deps/package.json +1 -1
  626. package/examples/rpc-extension-ui.ts +1 -1
  627. package/examples/sdk/06-extensions.ts +1 -1
  628. package/examples/sdk/README.md +1 -1
  629. package/npm-shrinkwrap.json +439 -405
  630. package/package.json +17 -17
  631. package/dist/server/create-harness.d.ts +0 -26
  632. package/dist/server/create-harness.d.ts.map +0 -1
  633. package/dist/server/create-harness.js +0 -107
  634. package/dist/server/create-harness.js.map +0 -1
@@ -0,0 +1,112 @@
1
+ import { spawn } from "node:child_process";
2
+ import { DEFAULT_HOOK_TIMEOUT_MS, } from "./types.js";
3
+ // A runaway formatter can emit megabytes; the decision that matters is at the
4
+ // start, so cap what we retain rather than what the child may write.
5
+ const MAX_HOOK_OUTPUT_CHARS = 1_000_000;
6
+ function truncate(text) {
7
+ return text.length > 200 ? `${text.slice(0, 200)}...` : text;
8
+ }
9
+ /**
10
+ * Shared decision parsing for command stdout and HTTP response bodies.
11
+ * Non-JSON output, a non-object, or an unknown decision value is "no decision"
12
+ * plus a warning -- never an allow (spec, Risks).
13
+ */
14
+ export function parseHookDecisionOutput(text) {
15
+ const trimmed = text.trim();
16
+ if (trimmed.length === 0)
17
+ return { ok: true };
18
+ let parsed;
19
+ try {
20
+ parsed = JSON.parse(trimmed);
21
+ }
22
+ catch {
23
+ return { ok: true, warning: `hook output was not valid JSON: ${truncate(trimmed)}` };
24
+ }
25
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
26
+ return { ok: true, warning: `hook output was not a JSON object: ${truncate(trimmed)}` };
27
+ }
28
+ const decision = parsed.decision;
29
+ if (decision === undefined)
30
+ return { ok: true };
31
+ if (decision !== "allow" && decision !== "block" && decision !== "ask") {
32
+ return { ok: true, warning: `hook output had an unknown decision: ${truncate(String(decision))}` };
33
+ }
34
+ const reason = parsed.reason;
35
+ return {
36
+ ok: true,
37
+ decision: { decision, ...(typeof reason === "string" && reason.length > 0 ? { reason } : {}) },
38
+ };
39
+ }
40
+ /**
41
+ * A command handler runs through the platform shell (`sh -c`, PowerShell on
42
+ * Windows) with the event payload on stdin and its decision on stdout.
43
+ *
44
+ * Exit-code table (behavioral reference:
45
+ * docs/research/2026-08-31-harness-landscape.md § 2.1): exit 0 + JSON = that
46
+ * decision; exit 0 + no output = no decision; exit 2 = block with stderr as
47
+ * the reason; any other nonzero exit (and spawn failure, and timeout) is a
48
+ * failure outcome, which the runtime turns into a fail-closed block.
49
+ */
50
+ export function commandHookHandler(config) {
51
+ const timeoutMs = config.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
52
+ const commandRef = truncate(config.command);
53
+ return {
54
+ async execute(payload) {
55
+ const shell = process.platform === "win32" ? "powershell.exe" : "sh";
56
+ const args = process.platform === "win32" ? ["-NoProfile", "-Command", config.command] : ["-c", config.command];
57
+ return await new Promise((resolve) => {
58
+ const child = spawn(shell, args, { stdio: ["pipe", "pipe", "pipe"] });
59
+ let stdout = "";
60
+ let stderr = "";
61
+ let settled = false;
62
+ const finish = (outcome) => {
63
+ if (settled)
64
+ return;
65
+ settled = true;
66
+ clearTimeout(timer);
67
+ resolve(outcome);
68
+ };
69
+ const timer = setTimeout(() => {
70
+ child.kill();
71
+ finish({ ok: false, warning: `hook timed out after ${timeoutMs}ms: ${commandRef}` });
72
+ }, timeoutMs);
73
+ child.stdout.on("data", (chunk) => {
74
+ if (stdout.length < MAX_HOOK_OUTPUT_CHARS)
75
+ stdout += chunk.toString("utf8");
76
+ });
77
+ child.stderr.on("data", (chunk) => {
78
+ if (stderr.length < MAX_HOOK_OUTPUT_CHARS)
79
+ stderr += chunk.toString("utf8");
80
+ });
81
+ // A hook that closes stdin early must not crash the write; the exit code decides.
82
+ child.stdin.on("error", () => { });
83
+ child.on("error", (error) => finish({ ok: false, warning: `hook failed to spawn: ${error.message}` }));
84
+ child.on("close", (code) => {
85
+ if (code === 0) {
86
+ finish(parseHookDecisionOutput(stdout));
87
+ }
88
+ else if (code === 2) {
89
+ finish({
90
+ ok: true,
91
+ decision: {
92
+ decision: "block",
93
+ reason: stderr.trim() || stdout.trim() || "blocked by hook (exit 2)",
94
+ },
95
+ });
96
+ }
97
+ else if (code === null) {
98
+ finish({ ok: false, warning: `hook terminated by a signal: ${commandRef}` });
99
+ }
100
+ else {
101
+ finish({
102
+ ok: false,
103
+ warning: `hook exited with exit code ${code}: ${stderr.trim() || stdout.trim() || commandRef}`,
104
+ });
105
+ }
106
+ });
107
+ child.stdin.end(JSON.stringify(payload));
108
+ });
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=command-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-handler.js","sourceRoot":"","sources":["../../../src/core/hooks/command-handler.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport {\n\tDEFAULT_HOOK_TIMEOUT_MS,\n\ttype HookCommandHandlerConfig,\n\ttype HookEventPayload,\n\ttype HookHandler,\n\ttype HookOutcome,\n} from \"./types.ts\";\n\n// A runaway formatter can emit megabytes; the decision that matters is at the\n// start, so cap what we retain rather than what the child may write.\nconst MAX_HOOK_OUTPUT_CHARS = 1_000_000;\n\nfunction truncate(text: string): string {\n\treturn text.length > 200 ? `${text.slice(0, 200)}...` : text;\n}\n\n/**\n * Shared decision parsing for command stdout and HTTP response bodies.\n * Non-JSON output, a non-object, or an unknown decision value is \"no decision\"\n * plus a warning -- never an allow (spec, Risks).\n */\nexport function parseHookDecisionOutput(text: string): HookOutcome {\n\tconst trimmed = text.trim();\n\tif (trimmed.length === 0) return { ok: true };\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(trimmed);\n\t} catch {\n\t\treturn { ok: true, warning: `hook output was not valid JSON: ${truncate(trimmed)}` };\n\t}\n\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\treturn { ok: true, warning: `hook output was not a JSON object: ${truncate(trimmed)}` };\n\t}\n\tconst decision = (parsed as { decision?: unknown }).decision;\n\tif (decision === undefined) return { ok: true };\n\tif (decision !== \"allow\" && decision !== \"block\" && decision !== \"ask\") {\n\t\treturn { ok: true, warning: `hook output had an unknown decision: ${truncate(String(decision))}` };\n\t}\n\tconst reason = (parsed as { reason?: unknown }).reason;\n\treturn {\n\t\tok: true,\n\t\tdecision: { decision, ...(typeof reason === \"string\" && reason.length > 0 ? { reason } : {}) },\n\t};\n}\n\n/**\n * A command handler runs through the platform shell (`sh -c`, PowerShell on\n * Windows) with the event payload on stdin and its decision on stdout.\n *\n * Exit-code table (behavioral reference:\n * docs/research/2026-08-31-harness-landscape.md § 2.1): exit 0 + JSON = that\n * decision; exit 0 + no output = no decision; exit 2 = block with stderr as\n * the reason; any other nonzero exit (and spawn failure, and timeout) is a\n * failure outcome, which the runtime turns into a fail-closed block.\n */\nexport function commandHookHandler(config: HookCommandHandlerConfig): HookHandler {\n\tconst timeoutMs = config.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;\n\tconst commandRef = truncate(config.command);\n\treturn {\n\t\tasync execute(payload: HookEventPayload): Promise<HookOutcome> {\n\t\t\tconst shell = process.platform === \"win32\" ? \"powershell.exe\" : \"sh\";\n\t\t\tconst args =\n\t\t\t\tprocess.platform === \"win32\" ? [\"-NoProfile\", \"-Command\", config.command] : [\"-c\", config.command];\n\t\t\treturn await new Promise<HookOutcome>((resolve) => {\n\t\t\t\tconst child = spawn(shell, args, { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\t\t\tlet stdout = \"\";\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tlet settled = false;\n\t\t\t\tconst finish = (outcome: HookOutcome) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tresolve(outcome);\n\t\t\t\t};\n\t\t\t\tconst timer = setTimeout(() => {\n\t\t\t\t\tchild.kill();\n\t\t\t\t\tfinish({ ok: false, warning: `hook timed out after ${timeoutMs}ms: ${commandRef}` });\n\t\t\t\t}, timeoutMs);\n\t\t\t\tchild.stdout.on(\"data\", (chunk: Buffer) => {\n\t\t\t\t\tif (stdout.length < MAX_HOOK_OUTPUT_CHARS) stdout += chunk.toString(\"utf8\");\n\t\t\t\t});\n\t\t\t\tchild.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\t\t\tif (stderr.length < MAX_HOOK_OUTPUT_CHARS) stderr += chunk.toString(\"utf8\");\n\t\t\t\t});\n\t\t\t\t// A hook that closes stdin early must not crash the write; the exit code decides.\n\t\t\t\tchild.stdin.on(\"error\", () => {});\n\t\t\t\tchild.on(\"error\", (error) => finish({ ok: false, warning: `hook failed to spawn: ${error.message}` }));\n\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\tif (code === 0) {\n\t\t\t\t\t\tfinish(parseHookDecisionOutput(stdout));\n\t\t\t\t\t} else if (code === 2) {\n\t\t\t\t\t\tfinish({\n\t\t\t\t\t\t\tok: true,\n\t\t\t\t\t\t\tdecision: {\n\t\t\t\t\t\t\t\tdecision: \"block\",\n\t\t\t\t\t\t\t\treason: stderr.trim() || stdout.trim() || \"blocked by hook (exit 2)\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (code === null) {\n\t\t\t\t\t\tfinish({ ok: false, warning: `hook terminated by a signal: ${commandRef}` });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfinish({\n\t\t\t\t\t\t\tok: false,\n\t\t\t\t\t\t\twarning: `hook exited with exit code ${code}: ${stderr.trim() || stdout.trim() || commandRef}`,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tchild.stdin.end(JSON.stringify(payload));\n\t\t\t});\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import { type HookHandler, type HookHttpHandlerConfig } from "./types.ts";
2
+ /**
3
+ * An HTTP handler POSTs the event payload and reads the decision from the 200
4
+ * response body. The URL comes from settings only -- no environment-variable
5
+ * interpolation into headers or URLs (spec, Non-goals). A non-200 response,
6
+ * a transport failure, or a timeout is a failure outcome, which the runtime
7
+ * turns into a fail-closed block on `tool_call`.
8
+ */
9
+ export declare function httpHookHandler(config: HookHttpHandlerConfig): HookHandler;
10
+ //# sourceMappingURL=http-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-handler.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/http-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAGN,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAE1B,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,WAAW,CAwB1E","sourcesContent":["import { parseHookDecisionOutput } from \"./command-handler.ts\";\nimport {\n\tDEFAULT_HOOK_TIMEOUT_MS,\n\ttype HookEventPayload,\n\ttype HookHandler,\n\ttype HookHttpHandlerConfig,\n\ttype HookOutcome,\n} from \"./types.ts\";\n\n/**\n * An HTTP handler POSTs the event payload and reads the decision from the 200\n * response body. The URL comes from settings only -- no environment-variable\n * interpolation into headers or URLs (spec, Non-goals). A non-200 response,\n * a transport failure, or a timeout is a failure outcome, which the runtime\n * turns into a fail-closed block on `tool_call`.\n */\nexport function httpHookHandler(config: HookHttpHandlerConfig): HookHandler {\n\tconst timeoutMs = config.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;\n\treturn {\n\t\tasync execute(payload: HookEventPayload): Promise<HookOutcome> {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(config.url, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn { ok: false, warning: `hook endpoint returned HTTP ${response.status}` };\n\t\t\t\t}\n\t\t\t\treturn parseHookDecisionOutput(await response.text());\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (/timeout|timed out|abort/i.test(message)) {\n\t\t\t\t\treturn { ok: false, warning: `hook timed out after ${timeoutMs}ms` };\n\t\t\t\t}\n\t\t\t\treturn { ok: false, warning: `hook request failed: ${message}` };\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import { parseHookDecisionOutput } from "./command-handler.js";
2
+ import { DEFAULT_HOOK_TIMEOUT_MS, } from "./types.js";
3
+ /**
4
+ * An HTTP handler POSTs the event payload and reads the decision from the 200
5
+ * response body. The URL comes from settings only -- no environment-variable
6
+ * interpolation into headers or URLs (spec, Non-goals). A non-200 response,
7
+ * a transport failure, or a timeout is a failure outcome, which the runtime
8
+ * turns into a fail-closed block on `tool_call`.
9
+ */
10
+ export function httpHookHandler(config) {
11
+ const timeoutMs = config.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
12
+ return {
13
+ async execute(payload) {
14
+ try {
15
+ const response = await fetch(config.url, {
16
+ method: "POST",
17
+ headers: { "content-type": "application/json" },
18
+ body: JSON.stringify(payload),
19
+ signal: AbortSignal.timeout(timeoutMs),
20
+ });
21
+ if (!response.ok) {
22
+ return { ok: false, warning: `hook endpoint returned HTTP ${response.status}` };
23
+ }
24
+ return parseHookDecisionOutput(await response.text());
25
+ }
26
+ catch (error) {
27
+ const message = error instanceof Error ? error.message : String(error);
28
+ if (/timeout|timed out|abort/i.test(message)) {
29
+ return { ok: false, warning: `hook timed out after ${timeoutMs}ms` };
30
+ }
31
+ return { ok: false, warning: `hook request failed: ${message}` };
32
+ }
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=http-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-handler.js","sourceRoot":"","sources":["../../../src/core/hooks/http-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EACN,uBAAuB,GAKvB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,MAA6B,EAAe;IAC3E,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,uBAAuB,CAAC;IAC9D,OAAO;QACN,KAAK,CAAC,OAAO,CAAC,OAAyB,EAAwB;YAC9D,IAAI,CAAC;gBACJ,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE;oBACxC,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;oBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;oBAC7B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC;iBACtC,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBAClB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,+BAA+B,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACjF,CAAC;gBACD,OAAO,uBAAuB,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACvD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACvE,IAAI,0BAA0B,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC9C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,SAAS,IAAI,EAAE,CAAC;gBACtE,CAAC;gBACD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,wBAAwB,OAAO,EAAE,EAAE,CAAC;YAClE,CAAC;QAAA,CACD;KACD,CAAC;AAAA,CACF","sourcesContent":["import { parseHookDecisionOutput } from \"./command-handler.ts\";\nimport {\n\tDEFAULT_HOOK_TIMEOUT_MS,\n\ttype HookEventPayload,\n\ttype HookHandler,\n\ttype HookHttpHandlerConfig,\n\ttype HookOutcome,\n} from \"./types.ts\";\n\n/**\n * An HTTP handler POSTs the event payload and reads the decision from the 200\n * response body. The URL comes from settings only -- no environment-variable\n * interpolation into headers or URLs (spec, Non-goals). A non-200 response,\n * a transport failure, or a timeout is a failure outcome, which the runtime\n * turns into a fail-closed block on `tool_call`.\n */\nexport function httpHookHandler(config: HookHttpHandlerConfig): HookHandler {\n\tconst timeoutMs = config.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;\n\treturn {\n\t\tasync execute(payload: HookEventPayload): Promise<HookOutcome> {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(config.url, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t\t\t});\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\treturn { ok: false, warning: `hook endpoint returned HTTP ${response.status}` };\n\t\t\t\t}\n\t\t\t\treturn parseHookDecisionOutput(await response.text());\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (/timeout|timed out|abort/i.test(message)) {\n\t\t\t\t\treturn { ok: false, warning: `hook timed out after ${timeoutMs}ms` };\n\t\t\t\t}\n\t\t\t\treturn { ok: false, warning: `hook request failed: ${message}` };\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Hook config parsing and runtime assembly. Parsing is strict and fails
3
+ * closed: an invalid `hooks` key throws `HookConfigError` with the offending
4
+ * path, and the session wiring lets that propagate -- a governance entry that
5
+ * silently loads as nothing is a policy hole, so a malformed key refuses to
6
+ * start the session instead (the permission-rule posture: a failed rule load
7
+ * blocks rather than falls open).
8
+ */
9
+ import { type HookEventName, type HookHandler, type HookHandlerConfig, type HookRuntime, type HooksSettings } from "./types.ts";
10
+ export declare class HookConfigError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ /**
14
+ * V1 matcher grammar: exact tool names joined by `|` or `,` with optional
15
+ * whitespace, mirroring permission-rule matching. Regex matchers are a
16
+ * recorded non-goal -- grammar creep is how config surfaces rot.
17
+ */
18
+ export declare function parseMatcher(matcher: string): (toolName: string) => boolean;
19
+ /** Strictly validate raw settings JSON into `HooksSettings`, throwing `HookConfigError` on the first problem. */
20
+ export declare function parseHookSettings(raw: unknown): HooksSettings;
21
+ export type HookHandlerFactory = (config: HookHandlerConfig, event: HookEventName) => HookHandler;
22
+ /**
23
+ * Assemble the runtime, or return undefined when nothing is configured -- an
24
+ * absent key constructs nothing, so an unconfigured session spawns no
25
+ * subprocess and changes no behavior. `createHandler` is the seam tests use to
26
+ * inject scripted handlers instead of real command/HTTP execution.
27
+ */
28
+ export declare function loadHookRuntime(settings: HooksSettings | undefined, createHandler?: HookHandlerFactory): HookRuntime | undefined;
29
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAEN,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,aAAa,EAElB,MAAM,YAAY,CAAC;AAEpB,qBAAa,eAAgB,SAAQ,KAAK;IACzC,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAU3E;AA8DD,iHAAiH;AACjH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAgB7D;AAED,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,aAAa,KAAK,WAAW,CAAC;AAMlG;;;;;GAKG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,aAAa,GAAE,kBAA8C,GAC3D,WAAW,GAAG,SAAS,CAgBzB","sourcesContent":["/**\n * Hook config parsing and runtime assembly. Parsing is strict and fails\n * closed: an invalid `hooks` key throws `HookConfigError` with the offending\n * path, and the session wiring lets that propagate -- a governance entry that\n * silently loads as nothing is a policy hole, so a malformed key refuses to\n * start the session instead (the permission-rule posture: a failed rule load\n * blocks rather than falls open).\n */\n\nimport { commandHookHandler } from \"./command-handler.ts\";\nimport { httpHookHandler } from \"./http-handler.ts\";\nimport { createHookRuntime, type RuntimeHookEntry } from \"./runtime.ts\";\nimport {\n\tHOOK_EVENT_NAMES,\n\ttype HookEventName,\n\ttype HookHandler,\n\ttype HookHandlerConfig,\n\ttype HookRuntime,\n\ttype HooksSettings,\n\tMAX_HOOK_TIMEOUT_MS,\n} from \"./types.ts\";\n\nexport class HookConfigError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"HookConfigError\";\n\t}\n}\n\nconst MATCHER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/**\n * V1 matcher grammar: exact tool names joined by `|` or `,` with optional\n * whitespace, mirroring permission-rule matching. Regex matchers are a\n * recorded non-goal -- grammar creep is how config surfaces rot.\n */\nexport function parseMatcher(matcher: string): (toolName: string) => boolean {\n\tconst names = matcher\n\t\t.split(/[|,]/)\n\t\t.map((name) => name.trim())\n\t\t.filter((name) => name.length > 0);\n\tif (names.length === 0 || !names.every((name) => MATCHER_NAME_PATTERN.test(name))) {\n\t\tthrow new HookConfigError(`invalid matcher \"${matcher}\" (expected exact tool names joined by \"|\" or \",\")`);\n\t}\n\tconst accepted = new Set(names);\n\treturn (toolName) => accepted.has(toolName);\n}\n\nfunction parseHandlerConfig(event: HookEventName, index: number, raw: unknown): HookHandlerConfig {\n\tconst path = `hooks.${event}[${index}]`;\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n\t\tthrow new HookConfigError(`${path}: expected an object`);\n\t}\n\tconst record = raw as Record<string, unknown>;\n\tconst type = record.type;\n\tif (type !== \"command\" && type !== \"http\") {\n\t\tthrow new HookConfigError(`${path}: \"type\" must be \"command\" or \"http\"`);\n\t}\n\tconst allowed =\n\t\ttype === \"command\" ? [\"type\", \"matcher\", \"command\", \"timeoutMs\"] : [\"type\", \"matcher\", \"url\", \"timeoutMs\"];\n\tfor (const key of Object.keys(record)) {\n\t\tif (!allowed.includes(key)) {\n\t\t\tthrow new HookConfigError(`${path}: unknown field \"${key}\" (a mistyped key would silently drop policy)`);\n\t\t}\n\t}\n\tconst matcherField = record.matcher;\n\tif (matcherField !== undefined) {\n\t\tif (typeof matcherField !== \"string\") {\n\t\t\tthrow new HookConfigError(`${path}: \"matcher\" must be a string`);\n\t\t}\n\t\t// Validate the grammar here, at load, so a bad matcher is rejected with\n\t\t// the config path even before any runtime is assembled.\n\t\tparseMatcher(matcherField);\n\t}\n\tconst timeoutField = record.timeoutMs;\n\tif (\n\t\ttimeoutField !== undefined &&\n\t\t(typeof timeoutField !== \"number\" ||\n\t\t\t!Number.isFinite(timeoutField) ||\n\t\t\ttimeoutField <= 0 ||\n\t\t\ttimeoutField > MAX_HOOK_TIMEOUT_MS)\n\t) {\n\t\tthrow new HookConfigError(`${path}: \"timeoutMs\" must be a number between 1 and ${MAX_HOOK_TIMEOUT_MS}`);\n\t}\n\tif (type === \"command\") {\n\t\tconst command = record.command;\n\t\tif (typeof command !== \"string\" || command.trim().length === 0) {\n\t\t\tthrow new HookConfigError(`${path}: \"command\" must be a non-empty string`);\n\t\t}\n\t\treturn {\n\t\t\ttype: \"command\",\n\t\t\tcommand,\n\t\t\t...(matcherField !== undefined ? { matcher: matcherField } : {}),\n\t\t\t...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),\n\t\t};\n\t}\n\tconst urlField = record.url;\n\tif (typeof urlField !== \"string\" || !URL.canParse(urlField) || !/^https?:$/.test(new URL(urlField).protocol)) {\n\t\tthrow new HookConfigError(`${path}: \"url\" must be an http(s) URL`);\n\t}\n\treturn {\n\t\ttype: \"http\",\n\t\turl: urlField,\n\t\t...(matcherField !== undefined ? { matcher: matcherField } : {}),\n\t\t...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),\n\t};\n}\n\n/** Strictly validate raw settings JSON into `HooksSettings`, throwing `HookConfigError` on the first problem. */\nexport function parseHookSettings(raw: unknown): HooksSettings {\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n\t\tthrow new HookConfigError(\"hooks: expected an object keyed by event name\");\n\t}\n\tconst parsed: HooksSettings = {};\n\tfor (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!(HOOK_EVENT_NAMES as readonly string[]).includes(key)) {\n\t\t\tthrow new HookConfigError(`hooks.\"${key}\": unknown hook event (v1 supports ${HOOK_EVENT_NAMES.join(\", \")})`);\n\t\t}\n\t\tconst event = key as HookEventName;\n\t\tif (!Array.isArray(value)) {\n\t\t\tthrow new HookConfigError(`hooks.${event}: expected an array of handler configs`);\n\t\t}\n\t\tparsed[event] = value.map((entry, index) => parseHandlerConfig(event, index, entry));\n\t}\n\treturn parsed;\n}\n\nexport type HookHandlerFactory = (config: HookHandlerConfig, event: HookEventName) => HookHandler;\n\nfunction defaultHookHandlerFactory(config: HookHandlerConfig): HookHandler {\n\treturn config.type === \"command\" ? commandHookHandler(config) : httpHookHandler(config);\n}\n\n/**\n * Assemble the runtime, or return undefined when nothing is configured -- an\n * absent key constructs nothing, so an unconfigured session spawns no\n * subprocess and changes no behavior. `createHandler` is the seam tests use to\n * inject scripted handlers instead of real command/HTTP execution.\n */\nexport function loadHookRuntime(\n\tsettings: HooksSettings | undefined,\n\tcreateHandler: HookHandlerFactory = defaultHookHandlerFactory,\n): HookRuntime | undefined {\n\tif (!settings) return undefined;\n\tconst entries: RuntimeHookEntry[] = [];\n\tfor (const event of HOOK_EVENT_NAMES) {\n\t\tconst configs = settings[event];\n\t\tif (!configs || configs.length === 0) continue;\n\t\tfor (const config of configs) {\n\t\t\tconst handler = createHandler(config, event);\n\t\t\tentries.push({\n\t\t\t\tevent,\n\t\t\t\thandler: config.matcher ? { ...handler, matchesTool: parseMatcher(config.matcher) } : handler,\n\t\t\t});\n\t\t}\n\t}\n\tif (entries.length === 0) return undefined;\n\treturn createHookRuntime(entries);\n}\n"]}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Hook config parsing and runtime assembly. Parsing is strict and fails
3
+ * closed: an invalid `hooks` key throws `HookConfigError` with the offending
4
+ * path, and the session wiring lets that propagate -- a governance entry that
5
+ * silently loads as nothing is a policy hole, so a malformed key refuses to
6
+ * start the session instead (the permission-rule posture: a failed rule load
7
+ * blocks rather than falls open).
8
+ */
9
+ import { commandHookHandler } from "./command-handler.js";
10
+ import { httpHookHandler } from "./http-handler.js";
11
+ import { createHookRuntime } from "./runtime.js";
12
+ import { HOOK_EVENT_NAMES, MAX_HOOK_TIMEOUT_MS, } from "./types.js";
13
+ export class HookConfigError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "HookConfigError";
17
+ }
18
+ }
19
+ const MATCHER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
20
+ /**
21
+ * V1 matcher grammar: exact tool names joined by `|` or `,` with optional
22
+ * whitespace, mirroring permission-rule matching. Regex matchers are a
23
+ * recorded non-goal -- grammar creep is how config surfaces rot.
24
+ */
25
+ export function parseMatcher(matcher) {
26
+ const names = matcher
27
+ .split(/[|,]/)
28
+ .map((name) => name.trim())
29
+ .filter((name) => name.length > 0);
30
+ if (names.length === 0 || !names.every((name) => MATCHER_NAME_PATTERN.test(name))) {
31
+ throw new HookConfigError(`invalid matcher "${matcher}" (expected exact tool names joined by "|" or ",")`);
32
+ }
33
+ const accepted = new Set(names);
34
+ return (toolName) => accepted.has(toolName);
35
+ }
36
+ function parseHandlerConfig(event, index, raw) {
37
+ const path = `hooks.${event}[${index}]`;
38
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
39
+ throw new HookConfigError(`${path}: expected an object`);
40
+ }
41
+ const record = raw;
42
+ const type = record.type;
43
+ if (type !== "command" && type !== "http") {
44
+ throw new HookConfigError(`${path}: "type" must be "command" or "http"`);
45
+ }
46
+ const allowed = type === "command" ? ["type", "matcher", "command", "timeoutMs"] : ["type", "matcher", "url", "timeoutMs"];
47
+ for (const key of Object.keys(record)) {
48
+ if (!allowed.includes(key)) {
49
+ throw new HookConfigError(`${path}: unknown field "${key}" (a mistyped key would silently drop policy)`);
50
+ }
51
+ }
52
+ const matcherField = record.matcher;
53
+ if (matcherField !== undefined) {
54
+ if (typeof matcherField !== "string") {
55
+ throw new HookConfigError(`${path}: "matcher" must be a string`);
56
+ }
57
+ // Validate the grammar here, at load, so a bad matcher is rejected with
58
+ // the config path even before any runtime is assembled.
59
+ parseMatcher(matcherField);
60
+ }
61
+ const timeoutField = record.timeoutMs;
62
+ if (timeoutField !== undefined &&
63
+ (typeof timeoutField !== "number" ||
64
+ !Number.isFinite(timeoutField) ||
65
+ timeoutField <= 0 ||
66
+ timeoutField > MAX_HOOK_TIMEOUT_MS)) {
67
+ throw new HookConfigError(`${path}: "timeoutMs" must be a number between 1 and ${MAX_HOOK_TIMEOUT_MS}`);
68
+ }
69
+ if (type === "command") {
70
+ const command = record.command;
71
+ if (typeof command !== "string" || command.trim().length === 0) {
72
+ throw new HookConfigError(`${path}: "command" must be a non-empty string`);
73
+ }
74
+ return {
75
+ type: "command",
76
+ command,
77
+ ...(matcherField !== undefined ? { matcher: matcherField } : {}),
78
+ ...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),
79
+ };
80
+ }
81
+ const urlField = record.url;
82
+ if (typeof urlField !== "string" || !URL.canParse(urlField) || !/^https?:$/.test(new URL(urlField).protocol)) {
83
+ throw new HookConfigError(`${path}: "url" must be an http(s) URL`);
84
+ }
85
+ return {
86
+ type: "http",
87
+ url: urlField,
88
+ ...(matcherField !== undefined ? { matcher: matcherField } : {}),
89
+ ...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),
90
+ };
91
+ }
92
+ /** Strictly validate raw settings JSON into `HooksSettings`, throwing `HookConfigError` on the first problem. */
93
+ export function parseHookSettings(raw) {
94
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
95
+ throw new HookConfigError("hooks: expected an object keyed by event name");
96
+ }
97
+ const parsed = {};
98
+ for (const [key, value] of Object.entries(raw)) {
99
+ if (!HOOK_EVENT_NAMES.includes(key)) {
100
+ throw new HookConfigError(`hooks."${key}": unknown hook event (v1 supports ${HOOK_EVENT_NAMES.join(", ")})`);
101
+ }
102
+ const event = key;
103
+ if (!Array.isArray(value)) {
104
+ throw new HookConfigError(`hooks.${event}: expected an array of handler configs`);
105
+ }
106
+ parsed[event] = value.map((entry, index) => parseHandlerConfig(event, index, entry));
107
+ }
108
+ return parsed;
109
+ }
110
+ function defaultHookHandlerFactory(config) {
111
+ return config.type === "command" ? commandHookHandler(config) : httpHookHandler(config);
112
+ }
113
+ /**
114
+ * Assemble the runtime, or return undefined when nothing is configured -- an
115
+ * absent key constructs nothing, so an unconfigured session spawns no
116
+ * subprocess and changes no behavior. `createHandler` is the seam tests use to
117
+ * inject scripted handlers instead of real command/HTTP execution.
118
+ */
119
+ export function loadHookRuntime(settings, createHandler = defaultHookHandlerFactory) {
120
+ if (!settings)
121
+ return undefined;
122
+ const entries = [];
123
+ for (const event of HOOK_EVENT_NAMES) {
124
+ const configs = settings[event];
125
+ if (!configs || configs.length === 0)
126
+ continue;
127
+ for (const config of configs) {
128
+ const handler = createHandler(config, event);
129
+ entries.push({
130
+ event,
131
+ handler: config.matcher ? { ...handler, matchesTool: parseMatcher(config.matcher) } : handler,
132
+ });
133
+ }
134
+ }
135
+ if (entries.length === 0)
136
+ return undefined;
137
+ return createHookRuntime(entries);
138
+ }
139
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/core/hooks/loader.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook config parsing and runtime assembly. Parsing is strict and fails\n * closed: an invalid `hooks` key throws `HookConfigError` with the offending\n * path, and the session wiring lets that propagate -- a governance entry that\n * silently loads as nothing is a policy hole, so a malformed key refuses to\n * start the session instead (the permission-rule posture: a failed rule load\n * blocks rather than falls open).\n */\n\nimport { commandHookHandler } from \"./command-handler.ts\";\nimport { httpHookHandler } from \"./http-handler.ts\";\nimport { createHookRuntime, type RuntimeHookEntry } from \"./runtime.ts\";\nimport {\n\tHOOK_EVENT_NAMES,\n\ttype HookEventName,\n\ttype HookHandler,\n\ttype HookHandlerConfig,\n\ttype HookRuntime,\n\ttype HooksSettings,\n\tMAX_HOOK_TIMEOUT_MS,\n} from \"./types.ts\";\n\nexport class HookConfigError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"HookConfigError\";\n\t}\n}\n\nconst MATCHER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;\n\n/**\n * V1 matcher grammar: exact tool names joined by `|` or `,` with optional\n * whitespace, mirroring permission-rule matching. Regex matchers are a\n * recorded non-goal -- grammar creep is how config surfaces rot.\n */\nexport function parseMatcher(matcher: string): (toolName: string) => boolean {\n\tconst names = matcher\n\t\t.split(/[|,]/)\n\t\t.map((name) => name.trim())\n\t\t.filter((name) => name.length > 0);\n\tif (names.length === 0 || !names.every((name) => MATCHER_NAME_PATTERN.test(name))) {\n\t\tthrow new HookConfigError(`invalid matcher \"${matcher}\" (expected exact tool names joined by \"|\" or \",\")`);\n\t}\n\tconst accepted = new Set(names);\n\treturn (toolName) => accepted.has(toolName);\n}\n\nfunction parseHandlerConfig(event: HookEventName, index: number, raw: unknown): HookHandlerConfig {\n\tconst path = `hooks.${event}[${index}]`;\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n\t\tthrow new HookConfigError(`${path}: expected an object`);\n\t}\n\tconst record = raw as Record<string, unknown>;\n\tconst type = record.type;\n\tif (type !== \"command\" && type !== \"http\") {\n\t\tthrow new HookConfigError(`${path}: \"type\" must be \"command\" or \"http\"`);\n\t}\n\tconst allowed =\n\t\ttype === \"command\" ? [\"type\", \"matcher\", \"command\", \"timeoutMs\"] : [\"type\", \"matcher\", \"url\", \"timeoutMs\"];\n\tfor (const key of Object.keys(record)) {\n\t\tif (!allowed.includes(key)) {\n\t\t\tthrow new HookConfigError(`${path}: unknown field \"${key}\" (a mistyped key would silently drop policy)`);\n\t\t}\n\t}\n\tconst matcherField = record.matcher;\n\tif (matcherField !== undefined) {\n\t\tif (typeof matcherField !== \"string\") {\n\t\t\tthrow new HookConfigError(`${path}: \"matcher\" must be a string`);\n\t\t}\n\t\t// Validate the grammar here, at load, so a bad matcher is rejected with\n\t\t// the config path even before any runtime is assembled.\n\t\tparseMatcher(matcherField);\n\t}\n\tconst timeoutField = record.timeoutMs;\n\tif (\n\t\ttimeoutField !== undefined &&\n\t\t(typeof timeoutField !== \"number\" ||\n\t\t\t!Number.isFinite(timeoutField) ||\n\t\t\ttimeoutField <= 0 ||\n\t\t\ttimeoutField > MAX_HOOK_TIMEOUT_MS)\n\t) {\n\t\tthrow new HookConfigError(`${path}: \"timeoutMs\" must be a number between 1 and ${MAX_HOOK_TIMEOUT_MS}`);\n\t}\n\tif (type === \"command\") {\n\t\tconst command = record.command;\n\t\tif (typeof command !== \"string\" || command.trim().length === 0) {\n\t\t\tthrow new HookConfigError(`${path}: \"command\" must be a non-empty string`);\n\t\t}\n\t\treturn {\n\t\t\ttype: \"command\",\n\t\t\tcommand,\n\t\t\t...(matcherField !== undefined ? { matcher: matcherField } : {}),\n\t\t\t...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),\n\t\t};\n\t}\n\tconst urlField = record.url;\n\tif (typeof urlField !== \"string\" || !URL.canParse(urlField) || !/^https?:$/.test(new URL(urlField).protocol)) {\n\t\tthrow new HookConfigError(`${path}: \"url\" must be an http(s) URL`);\n\t}\n\treturn {\n\t\ttype: \"http\",\n\t\turl: urlField,\n\t\t...(matcherField !== undefined ? { matcher: matcherField } : {}),\n\t\t...(timeoutField !== undefined ? { timeoutMs: timeoutField } : {}),\n\t};\n}\n\n/** Strictly validate raw settings JSON into `HooksSettings`, throwing `HookConfigError` on the first problem. */\nexport function parseHookSettings(raw: unknown): HooksSettings {\n\tif (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n\t\tthrow new HookConfigError(\"hooks: expected an object keyed by event name\");\n\t}\n\tconst parsed: HooksSettings = {};\n\tfor (const [key, value] of Object.entries(raw as Record<string, unknown>)) {\n\t\tif (!(HOOK_EVENT_NAMES as readonly string[]).includes(key)) {\n\t\t\tthrow new HookConfigError(`hooks.\"${key}\": unknown hook event (v1 supports ${HOOK_EVENT_NAMES.join(\", \")})`);\n\t\t}\n\t\tconst event = key as HookEventName;\n\t\tif (!Array.isArray(value)) {\n\t\t\tthrow new HookConfigError(`hooks.${event}: expected an array of handler configs`);\n\t\t}\n\t\tparsed[event] = value.map((entry, index) => parseHandlerConfig(event, index, entry));\n\t}\n\treturn parsed;\n}\n\nexport type HookHandlerFactory = (config: HookHandlerConfig, event: HookEventName) => HookHandler;\n\nfunction defaultHookHandlerFactory(config: HookHandlerConfig): HookHandler {\n\treturn config.type === \"command\" ? commandHookHandler(config) : httpHookHandler(config);\n}\n\n/**\n * Assemble the runtime, or return undefined when nothing is configured -- an\n * absent key constructs nothing, so an unconfigured session spawns no\n * subprocess and changes no behavior. `createHandler` is the seam tests use to\n * inject scripted handlers instead of real command/HTTP execution.\n */\nexport function loadHookRuntime(\n\tsettings: HooksSettings | undefined,\n\tcreateHandler: HookHandlerFactory = defaultHookHandlerFactory,\n): HookRuntime | undefined {\n\tif (!settings) return undefined;\n\tconst entries: RuntimeHookEntry[] = [];\n\tfor (const event of HOOK_EVENT_NAMES) {\n\t\tconst configs = settings[event];\n\t\tif (!configs || configs.length === 0) continue;\n\t\tfor (const config of configs) {\n\t\t\tconst handler = createHandler(config, event);\n\t\t\tentries.push({\n\t\t\t\tevent,\n\t\t\t\thandler: config.matcher ? { ...handler, matchesTool: parseMatcher(config.matcher) } : handler,\n\t\t\t});\n\t\t}\n\t}\n\tif (entries.length === 0) return undefined;\n\treturn createHookRuntime(entries);\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The hook runtime: an immutable handler list keyed by event, plus the two
3
+ * emission shapes the session seam consumes. Decision logic lives here so the
4
+ * restriction-only rule is enforced in exactly one place -- `decideToolCall`
5
+ * returns a block or nothing, never an allow that could bypass the gate.
6
+ */
7
+ import type { HookEventName, HookHandler, HookRuntime } from "./types.ts";
8
+ export interface RuntimeHookEntry {
9
+ event: HookEventName;
10
+ handler: HookHandler;
11
+ }
12
+ export declare function createHookRuntime(entries: RuntimeHookEntry[]): HookRuntime;
13
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,WAAW,CAAC;CACrB;AAQD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,WAAW,CA6C1E","sourcesContent":["/**\n * The hook runtime: an immutable handler list keyed by event, plus the two\n * emission shapes the session seam consumes. Decision logic lives here so the\n * restriction-only rule is enforced in exactly one place -- `decideToolCall`\n * returns a block or nothing, never an allow that could bypass the gate.\n */\n\nimport type { HookEventName, HookHandler, HookRuntime } from \"./types.ts\";\n\nexport interface RuntimeHookEntry {\n\tevent: HookEventName;\n\thandler: HookHandler;\n}\n\nfunction accepted(handler: HookHandler, toolName: string | undefined): boolean {\n\tif (!handler.matchesTool) return true;\n\tif (toolName === undefined) return true;\n\treturn handler.matchesTool(toolName);\n}\n\nexport function createHookRuntime(entries: RuntimeHookEntry[]): HookRuntime {\n\tconst byEvent = new Map<HookEventName, HookHandler[]>();\n\tfor (const entry of entries) {\n\t\tconst handlers = byEvent.get(entry.event);\n\t\tif (handlers) {\n\t\t\thandlers.push(entry.handler);\n\t\t} else {\n\t\t\tbyEvent.set(entry.event, [entry.handler]);\n\t\t}\n\t}\n\n\treturn {\n\t\thasHandlers(event) {\n\t\t\treturn (byEvent.get(event)?.length ?? 0) > 0;\n\t\t},\n\t\thandlersFor(event) {\n\t\t\treturn byEvent.get(event) ?? [];\n\t\t},\n\t\tasync decideToolCall(toolName, payload) {\n\t\t\tfor (const handler of byEvent.get(\"tool_call\") ?? []) {\n\t\t\t\tif (!accepted(handler, toolName)) continue;\n\t\t\t\tconst outcome = await handler.execute(payload);\n\t\t\t\tif (!outcome.ok) {\n\t\t\t\t\t// Fail closed: a hook that cannot run must not widen authority.\n\t\t\t\t\t// This is the spawn-failure and timeout path too.\n\t\t\t\t\treturn { block: true, reason: outcome.warning };\n\t\t\t\t}\n\t\t\t\t// Restriction-only (spec): `block` short-circuits; `allow` and\n\t\t\t\t// `ask` fall through so the permission gate stays the last check.\n\t\t\t\tif (outcome.decision?.decision === \"block\") {\n\t\t\t\t\treturn { block: true, reason: outcome.decision.reason ?? \"blocked by a declarative hook\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t\tasync emitObserve(event, payload) {\n\t\t\tfor (const handler of byEvent.get(event) ?? []) {\n\t\t\t\tif (!accepted(handler, payload.toolName)) continue;\n\t\t\t\t// Observe-only: awaited for ordering, but a failure is swallowed\n\t\t\t\t// because no decision is read from these events.\n\t\t\t\tconst outcome = await handler.execute(payload);\n\t\t\t\tif (!outcome.ok) continue;\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The hook runtime: an immutable handler list keyed by event, plus the two
3
+ * emission shapes the session seam consumes. Decision logic lives here so the
4
+ * restriction-only rule is enforced in exactly one place -- `decideToolCall`
5
+ * returns a block or nothing, never an allow that could bypass the gate.
6
+ */
7
+ function accepted(handler, toolName) {
8
+ if (!handler.matchesTool)
9
+ return true;
10
+ if (toolName === undefined)
11
+ return true;
12
+ return handler.matchesTool(toolName);
13
+ }
14
+ export function createHookRuntime(entries) {
15
+ const byEvent = new Map();
16
+ for (const entry of entries) {
17
+ const handlers = byEvent.get(entry.event);
18
+ if (handlers) {
19
+ handlers.push(entry.handler);
20
+ }
21
+ else {
22
+ byEvent.set(entry.event, [entry.handler]);
23
+ }
24
+ }
25
+ return {
26
+ hasHandlers(event) {
27
+ return (byEvent.get(event)?.length ?? 0) > 0;
28
+ },
29
+ handlersFor(event) {
30
+ return byEvent.get(event) ?? [];
31
+ },
32
+ async decideToolCall(toolName, payload) {
33
+ for (const handler of byEvent.get("tool_call") ?? []) {
34
+ if (!accepted(handler, toolName))
35
+ continue;
36
+ const outcome = await handler.execute(payload);
37
+ if (!outcome.ok) {
38
+ // Fail closed: a hook that cannot run must not widen authority.
39
+ // This is the spawn-failure and timeout path too.
40
+ return { block: true, reason: outcome.warning };
41
+ }
42
+ // Restriction-only (spec): `block` short-circuits; `allow` and
43
+ // `ask` fall through so the permission gate stays the last check.
44
+ if (outcome.decision?.decision === "block") {
45
+ return { block: true, reason: outcome.decision.reason ?? "blocked by a declarative hook" };
46
+ }
47
+ }
48
+ return undefined;
49
+ },
50
+ async emitObserve(event, payload) {
51
+ for (const handler of byEvent.get(event) ?? []) {
52
+ if (!accepted(handler, payload.toolName))
53
+ continue;
54
+ // Observe-only: awaited for ordering, but a failure is swallowed
55
+ // because no decision is read from these events.
56
+ const outcome = await handler.execute(payload);
57
+ if (!outcome.ok)
58
+ continue;
59
+ }
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/core/hooks/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,SAAS,QAAQ,CAAC,OAAoB,EAAE,QAA4B,EAAW;IAC9E,IAAI,CAAC,OAAO,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAAA,CACrC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAA2B,EAAe;IAC3E,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgC,CAAC;IACxD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3C,CAAC;IACF,CAAC;IAED,OAAO;QACN,WAAW,CAAC,KAAK,EAAE;YAClB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAAA,CAC7C;QACD,WAAW,CAAC,KAAK,EAAE;YAClB,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAAA,CAChC;QACD,KAAK,CAAC,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE;YACvC,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;gBACtD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;oBAAE,SAAS;gBAC3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;oBACjB,gEAAgE;oBAChE,kDAAkD;oBAClD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;gBACjD,CAAC;gBACD,+DAA+D;gBAC/D,kEAAkE;gBAClE,IAAI,OAAO,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,EAAE,CAAC;oBAC5C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,IAAI,+BAA+B,EAAE,CAAC;gBAC5F,CAAC;YACF,CAAC;YACD,OAAO,SAAS,CAAC;QAAA,CACjB;QACD,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE;YACjC,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACnD,iEAAiE;gBACjE,iDAAiD;gBACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,EAAE;oBAAE,SAAS;YAC3B,CAAC;QAAA,CACD;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * The hook runtime: an immutable handler list keyed by event, plus the two\n * emission shapes the session seam consumes. Decision logic lives here so the\n * restriction-only rule is enforced in exactly one place -- `decideToolCall`\n * returns a block or nothing, never an allow that could bypass the gate.\n */\n\nimport type { HookEventName, HookHandler, HookRuntime } from \"./types.ts\";\n\nexport interface RuntimeHookEntry {\n\tevent: HookEventName;\n\thandler: HookHandler;\n}\n\nfunction accepted(handler: HookHandler, toolName: string | undefined): boolean {\n\tif (!handler.matchesTool) return true;\n\tif (toolName === undefined) return true;\n\treturn handler.matchesTool(toolName);\n}\n\nexport function createHookRuntime(entries: RuntimeHookEntry[]): HookRuntime {\n\tconst byEvent = new Map<HookEventName, HookHandler[]>();\n\tfor (const entry of entries) {\n\t\tconst handlers = byEvent.get(entry.event);\n\t\tif (handlers) {\n\t\t\thandlers.push(entry.handler);\n\t\t} else {\n\t\t\tbyEvent.set(entry.event, [entry.handler]);\n\t\t}\n\t}\n\n\treturn {\n\t\thasHandlers(event) {\n\t\t\treturn (byEvent.get(event)?.length ?? 0) > 0;\n\t\t},\n\t\thandlersFor(event) {\n\t\t\treturn byEvent.get(event) ?? [];\n\t\t},\n\t\tasync decideToolCall(toolName, payload) {\n\t\t\tfor (const handler of byEvent.get(\"tool_call\") ?? []) {\n\t\t\t\tif (!accepted(handler, toolName)) continue;\n\t\t\t\tconst outcome = await handler.execute(payload);\n\t\t\t\tif (!outcome.ok) {\n\t\t\t\t\t// Fail closed: a hook that cannot run must not widen authority.\n\t\t\t\t\t// This is the spawn-failure and timeout path too.\n\t\t\t\t\treturn { block: true, reason: outcome.warning };\n\t\t\t\t}\n\t\t\t\t// Restriction-only (spec): `block` short-circuits; `allow` and\n\t\t\t\t// `ask` fall through so the permission gate stays the last check.\n\t\t\t\tif (outcome.decision?.decision === \"block\") {\n\t\t\t\t\treturn { block: true, reason: outcome.decision.reason ?? \"blocked by a declarative hook\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t\tasync emitObserve(event, payload) {\n\t\t\tfor (const handler of byEvent.get(event) ?? []) {\n\t\t\t\tif (!accepted(handler, payload.toolName)) continue;\n\t\t\t\t// Observe-only: awaited for ordering, but a failure is swallowed\n\t\t\t\t// because no decision is read from these events.\n\t\t\t\tconst outcome = await handler.execute(payload);\n\t\t\t\tif (!outcome.ok) continue;\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Declarative hooks (spec 2026-08-31-declarative-hooks): a settings-file path
3
+ * onto the extension event catalog, for operators who will never author a
4
+ * TypeScript extension. Version one maps five events onto command and HTTP
5
+ * handlers, with decisions read only from `tool_call`.
6
+ *
7
+ * The decision vocabulary is restriction-only: `block` short-circuits, `ask`
8
+ * defers to the permission gate, and `allow` is recorded but never bypasses
9
+ * the gate. A hook can narrow what runs, never widen it -- which is what makes
10
+ * trust-gated project-scope hooks safe where project-sandbox profiles are not
11
+ * (ADR 0016).
12
+ */
13
+ export declare const HOOK_EVENT_NAMES: readonly ["tool_call", "tool_result", "session_start", "turn_end", "session_before_compact"];
14
+ export type HookEventName = (typeof HOOK_EVENT_NAMES)[number];
15
+ export interface HookCommandHandlerConfig {
16
+ type: "command";
17
+ /** Tool-name exact or `|`/`,`-separated list; absent matches every tool. Only consulted for events that carry a tool name. */
18
+ matcher?: string;
19
+ /** Shell command; the event payload arrives as JSON on stdin, the decision as JSON on stdout. */
20
+ command: string;
21
+ /** Default 10s (DEFAULT_HOOK_TIMEOUT_MS). A handler that outlives its timeout fails closed on `tool_call`. */
22
+ timeoutMs?: number;
23
+ }
24
+ export interface HookHttpHandlerConfig {
25
+ type: "http";
26
+ matcher?: string;
27
+ /** The event payload is POSTed here; the decision is read from the 200 response body. URLs come from settings only -- never environment interpolation. */
28
+ url: string;
29
+ timeoutMs?: number;
30
+ }
31
+ export type HookHandlerConfig = HookCommandHandlerConfig | HookHttpHandlerConfig;
32
+ export type HooksSettings = Partial<Record<HookEventName, HookHandlerConfig[]>>;
33
+ export interface HookDecision {
34
+ decision: "allow" | "block" | "ask";
35
+ reason?: string;
36
+ }
37
+ export interface HookEventPayload {
38
+ type: HookEventName;
39
+ toolName?: string;
40
+ toolCallId?: string;
41
+ input?: unknown;
42
+ reason?: string;
43
+ }
44
+ export type HookOutcome = {
45
+ ok: true;
46
+ decision?: HookDecision;
47
+ warning?: string;
48
+ } | {
49
+ ok: false;
50
+ warning: string;
51
+ };
52
+ export interface HookHandler {
53
+ /** Absent on a handler means it matches every tool. */
54
+ matchesTool?(toolName: string): boolean;
55
+ execute(payload: HookEventPayload): Promise<HookOutcome>;
56
+ }
57
+ /** What a `tool_call` hook hands back to the session: the same shape the beforeToolCall seam already consumes. */
58
+ export interface HookToolCallBlock {
59
+ block: true;
60
+ reason: string;
61
+ }
62
+ export interface HookRuntime {
63
+ hasHandlers(event: HookEventName): boolean;
64
+ /** Test and diagnostic accessor; the runtime never mutates the handlers it holds. */
65
+ handlersFor(event: HookEventName): readonly HookHandler[];
66
+ /**
67
+ * `tool_call` decisions, restriction-only: `block` returns a block for the
68
+ * seam to consume, every other outcome (allow, ask, no decision) returns
69
+ * undefined so the permission gate still evaluates the call. A handler that
70
+ * fails or times out fails closed.
71
+ */
72
+ decideToolCall(toolName: string, payload: HookEventPayload): Promise<HookToolCallBlock | undefined>;
73
+ /**
74
+ * Observe-only emission for `tool_result` and lifecycle events: handlers are
75
+ * awaited in order, failures are swallowed, and no decision is ever read.
76
+ */
77
+ emitObserve(event: HookEventName, payload: HookEventPayload): Promise<void>;
78
+ }
79
+ /** Default per-handler timeout. A hung formatter must not stall the loop indefinitely. */
80
+ export declare const DEFAULT_HOOK_TIMEOUT_MS = 10000;
81
+ /** Hard cap on configured timeouts. */
82
+ export declare const MAX_HOOK_TIMEOUT_MS = 60000;
83
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,gBAAgB,8FAMnB,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,SAAS,CAAC;IAChB,8HAA8H;IAC9H,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iGAAiG;IACjG,OAAO,EAAE,MAAM,CAAC;IAChB,8GAA8G;IAC9G,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0JAA0J;IAC1J,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,iBAAiB,GAAG,wBAAwB,GAAG,qBAAqB,CAAC;AAEjF,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;AAEhF,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnH,MAAM,WAAW,WAAW;IAC3B,uDAAuD;IACvD,WAAW,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CACzD;AAED,kHAAkH;AAClH,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,IAAI,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC3B,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC;IAC3C,qFAAqF;IACrF,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,WAAW,EAAE,CAAC;IAC1D;;;;;OAKG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IACpG;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E;AAED,0FAA0F;AAC1F,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAC9C,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,QAAS,CAAC","sourcesContent":["/**\n * Declarative hooks (spec 2026-08-31-declarative-hooks): a settings-file path\n * onto the extension event catalog, for operators who will never author a\n * TypeScript extension. Version one maps five events onto command and HTTP\n * handlers, with decisions read only from `tool_call`.\n *\n * The decision vocabulary is restriction-only: `block` short-circuits, `ask`\n * defers to the permission gate, and `allow` is recorded but never bypasses\n * the gate. A hook can narrow what runs, never widen it -- which is what makes\n * trust-gated project-scope hooks safe where project-sandbox profiles are not\n * (ADR 0016).\n */\n\nexport const HOOK_EVENT_NAMES = [\n\t\"tool_call\",\n\t\"tool_result\",\n\t\"session_start\",\n\t\"turn_end\",\n\t\"session_before_compact\",\n] as const;\nexport type HookEventName = (typeof HOOK_EVENT_NAMES)[number];\n\nexport interface HookCommandHandlerConfig {\n\ttype: \"command\";\n\t/** Tool-name exact or `|`/`,`-separated list; absent matches every tool. Only consulted for events that carry a tool name. */\n\tmatcher?: string;\n\t/** Shell command; the event payload arrives as JSON on stdin, the decision as JSON on stdout. */\n\tcommand: string;\n\t/** Default 10s (DEFAULT_HOOK_TIMEOUT_MS). A handler that outlives its timeout fails closed on `tool_call`. */\n\ttimeoutMs?: number;\n}\n\nexport interface HookHttpHandlerConfig {\n\ttype: \"http\";\n\tmatcher?: string;\n\t/** The event payload is POSTed here; the decision is read from the 200 response body. URLs come from settings only -- never environment interpolation. */\n\turl: string;\n\ttimeoutMs?: number;\n}\n\nexport type HookHandlerConfig = HookCommandHandlerConfig | HookHttpHandlerConfig;\n\nexport type HooksSettings = Partial<Record<HookEventName, HookHandlerConfig[]>>;\n\nexport interface HookDecision {\n\tdecision: \"allow\" | \"block\" | \"ask\";\n\treason?: string;\n}\n\nexport interface HookEventPayload {\n\ttype: HookEventName;\n\ttoolName?: string;\n\ttoolCallId?: string;\n\tinput?: unknown;\n\treason?: string;\n}\n\nexport type HookOutcome = { ok: true; decision?: HookDecision; warning?: string } | { ok: false; warning: string };\n\nexport interface HookHandler {\n\t/** Absent on a handler means it matches every tool. */\n\tmatchesTool?(toolName: string): boolean;\n\texecute(payload: HookEventPayload): Promise<HookOutcome>;\n}\n\n/** What a `tool_call` hook hands back to the session: the same shape the beforeToolCall seam already consumes. */\nexport interface HookToolCallBlock {\n\tblock: true;\n\treason: string;\n}\n\nexport interface HookRuntime {\n\thasHandlers(event: HookEventName): boolean;\n\t/** Test and diagnostic accessor; the runtime never mutates the handlers it holds. */\n\thandlersFor(event: HookEventName): readonly HookHandler[];\n\t/**\n\t * `tool_call` decisions, restriction-only: `block` returns a block for the\n\t * seam to consume, every other outcome (allow, ask, no decision) returns\n\t * undefined so the permission gate still evaluates the call. A handler that\n\t * fails or times out fails closed.\n\t */\n\tdecideToolCall(toolName: string, payload: HookEventPayload): Promise<HookToolCallBlock | undefined>;\n\t/**\n\t * Observe-only emission for `tool_result` and lifecycle events: handlers are\n\t * awaited in order, failures are swallowed, and no decision is ever read.\n\t */\n\temitObserve(event: HookEventName, payload: HookEventPayload): Promise<void>;\n}\n\n/** Default per-handler timeout. A hung formatter must not stall the loop indefinitely. */\nexport const DEFAULT_HOOK_TIMEOUT_MS = 10_000;\n/** Hard cap on configured timeouts. */\nexport const MAX_HOOK_TIMEOUT_MS = 60_000;\n"]}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Declarative hooks (spec 2026-08-31-declarative-hooks): a settings-file path
3
+ * onto the extension event catalog, for operators who will never author a
4
+ * TypeScript extension. Version one maps five events onto command and HTTP
5
+ * handlers, with decisions read only from `tool_call`.
6
+ *
7
+ * The decision vocabulary is restriction-only: `block` short-circuits, `ask`
8
+ * defers to the permission gate, and `allow` is recorded but never bypasses
9
+ * the gate. A hook can narrow what runs, never widen it -- which is what makes
10
+ * trust-gated project-scope hooks safe where project-sandbox profiles are not
11
+ * (ADR 0016).
12
+ */
13
+ export const HOOK_EVENT_NAMES = [
14
+ "tool_call",
15
+ "tool_result",
16
+ "session_start",
17
+ "turn_end",
18
+ "session_before_compact",
19
+ ];
20
+ /** Default per-handler timeout. A hung formatter must not stall the loop indefinitely. */
21
+ export const DEFAULT_HOOK_TIMEOUT_MS = 10_000;
22
+ /** Hard cap on configured timeouts. */
23
+ export const MAX_HOOK_TIMEOUT_MS = 60_000;
24
+ //# sourceMappingURL=types.js.map