@wrongstack/core 0.313.0 → 0.316.1

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 (343) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/legacy-journal-import.d.ts +0 -6
  3. package/dist/chronicle/metrics-ingest.d.ts +14 -0
  4. package/dist/chronicle/metrics-schema.d.ts +10 -1
  5. package/dist/chronicle/metrics-store.d.ts +20 -2
  6. package/dist/chronicle/project-server.js +98 -4874
  7. package/dist/chronicle/sink.d.ts +13 -0
  8. package/dist/chronicle/sqlite-journal.d.ts +20 -2
  9. package/dist/chunk-25LSLC3G.js +334 -0
  10. package/dist/chunk-27QIURLN.js +1073 -0
  11. package/dist/chunk-2FK4WTFF.js +207 -0
  12. package/dist/chunk-2YU4WVBW.js +134 -0
  13. package/dist/chunk-3AGWVSS7.js +4608 -0
  14. package/dist/chunk-3BDV7RL3.js +211 -0
  15. package/dist/chunk-3ECENVHL.js +325 -0
  16. package/dist/chunk-3XPVB7BA.js +135 -0
  17. package/dist/chunk-45WI72ZB.js +16884 -0
  18. package/dist/chunk-4BMVPOHO.js +3748 -0
  19. package/dist/chunk-4I7HGVVA.js +363 -0
  20. package/dist/chunk-4LCZVJVF.js +317 -0
  21. package/dist/chunk-4SJNS7OB.js +217 -0
  22. package/dist/chunk-4VWO3ZXL.js +342 -0
  23. package/dist/chunk-57GEWK5X.js +26 -0
  24. package/dist/chunk-5BMIE7QG.js +46 -0
  25. package/dist/chunk-5S437PRC.js +50 -0
  26. package/dist/chunk-5XDUGTKN.js +511 -0
  27. package/dist/chunk-5YPSXQXM.js +1443 -0
  28. package/dist/chunk-6FHWXLPT.js +10 -0
  29. package/dist/chunk-6H5GEOET.js +2406 -0
  30. package/dist/chunk-6NWEVFEL.js +151 -0
  31. package/dist/chunk-AYW5YINX.js +58 -0
  32. package/dist/chunk-B4AMLZPB.js +9 -0
  33. package/dist/chunk-B7F4WVP4.js +65 -0
  34. package/dist/chunk-BJHVULHO.js +55 -0
  35. package/dist/chunk-BPBKKBUK.js +2085 -0
  36. package/dist/chunk-BYD345OX.js +589 -0
  37. package/dist/chunk-C27KDLOX.js +279 -0
  38. package/dist/chunk-CU546ESW.js +597 -0
  39. package/dist/chunk-DLHHXUAM.js +172 -0
  40. package/dist/chunk-DOTHQSJ2.js +429 -0
  41. package/dist/chunk-DYLPMV4Q.js +711 -0
  42. package/dist/chunk-EODDVJBM.js +82 -0
  43. package/dist/chunk-F3JWNST7.js +361 -0
  44. package/dist/chunk-FC2ZGRF3.js +140 -0
  45. package/dist/chunk-FCBTO76Z.js +5786 -0
  46. package/dist/chunk-FHZVFYSL.js +396 -0
  47. package/dist/chunk-FNXVT7HF.js +52 -0
  48. package/dist/chunk-FWNLEZYG.js +259 -0
  49. package/dist/chunk-FXQLIX3K.js +211 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GDQ7CZRQ.js +116 -0
  52. package/dist/chunk-GJJGJB2R.js +1374 -0
  53. package/dist/chunk-GTCU6DZ3.js +1 -0
  54. package/dist/chunk-HA4ZYA6T.js +178 -0
  55. package/dist/chunk-HI3JGZR2.js +188 -0
  56. package/dist/chunk-HMLIGAU7.js +395 -0
  57. package/dist/chunk-HXKZ7OBQ.js +303 -0
  58. package/dist/chunk-I7DALG7J.js +292 -0
  59. package/dist/chunk-IRUTEHCE.js +960 -0
  60. package/dist/chunk-IYFBFX34.js +86 -0
  61. package/dist/chunk-J2WRWR37.js +1104 -0
  62. package/dist/chunk-JFPJ6LLQ.js +129 -0
  63. package/dist/chunk-JMULFFLN.js +9 -0
  64. package/dist/chunk-JO4MGB6Z.js +1264 -0
  65. package/dist/chunk-JVKB2TBL.js +48 -0
  66. package/dist/chunk-JX7VBZDK.js +31 -0
  67. package/dist/chunk-K6BXGITA.js +931 -0
  68. package/dist/chunk-KDQJCT2J.js +45 -0
  69. package/dist/chunk-KJ2ZTQSL.js +47 -0
  70. package/dist/chunk-KZMSZV32.js +187 -0
  71. package/dist/chunk-LA7CLDRW.js +129 -0
  72. package/dist/chunk-LHOA5DWA.js +28 -0
  73. package/dist/chunk-LSMMOS2Z.js +119 -0
  74. package/dist/chunk-LX5QWLBL.js +27 -0
  75. package/dist/chunk-LXWCMQNJ.js +789 -0
  76. package/dist/chunk-M27LIEKV.js +2829 -0
  77. package/dist/chunk-MJPHJRAL.js +663 -0
  78. package/dist/chunk-MTL4O5YR.js +228 -0
  79. package/dist/chunk-NDBIPFNY.js +102 -0
  80. package/dist/chunk-NEMIWEXC.js +1199 -0
  81. package/dist/chunk-NHJ75HXY.js +1 -0
  82. package/dist/chunk-NIOAGFPB.js +1510 -0
  83. package/dist/chunk-NS52ISWS.js +355 -0
  84. package/dist/chunk-NSOFPKGT.js +5352 -0
  85. package/dist/chunk-NVIZG63R.js +757 -0
  86. package/dist/chunk-O37BQ65S.js +21 -0
  87. package/dist/chunk-OOYZPOXM.js +308 -0
  88. package/dist/chunk-OSGLYW5J.js +28 -0
  89. package/dist/chunk-PFCMSSUF.js +1640 -0
  90. package/dist/chunk-PFZO3C6H.js +14 -0
  91. package/dist/chunk-PGRVXIMY.js +158 -0
  92. package/dist/chunk-PHH7LD45.js +113 -0
  93. package/dist/chunk-PISBFBJN.js +109 -0
  94. package/dist/chunk-POOOIWE5.js +9 -0
  95. package/dist/chunk-PQNS3U7Z.js +1240 -0
  96. package/dist/chunk-QSTSJOCK.js +402 -0
  97. package/dist/chunk-RFQMOKWK.js +282 -0
  98. package/dist/chunk-RJIZEJ42.js +30 -0
  99. package/dist/chunk-RTOTNK3P.js +484 -0
  100. package/dist/chunk-RXKSDUAU.js +1172 -0
  101. package/dist/chunk-S45JPKQN.js +3317 -0
  102. package/dist/chunk-SAFHACFA.js +152 -0
  103. package/dist/chunk-SQJVFEBV.js +41 -0
  104. package/dist/chunk-SVT4RNTR.js +49 -0
  105. package/dist/chunk-SWUFN7XM.js +43 -0
  106. package/dist/chunk-SXMVYC5H.js +174 -0
  107. package/dist/chunk-SZ6AYUIZ.js +23 -0
  108. package/dist/chunk-TBRL2RCC.js +9 -0
  109. package/dist/chunk-TCCHINEF.js +655 -0
  110. package/dist/chunk-THEGTRAG.js +316 -0
  111. package/dist/chunk-TR4LQE7L.js +2256 -0
  112. package/dist/chunk-TRT2HRWZ.js +18 -0
  113. package/dist/chunk-TWJMGXCI.js +900 -0
  114. package/dist/chunk-U45AXT5D.js +1023 -0
  115. package/dist/chunk-U4FWBSO3.js +509 -0
  116. package/dist/chunk-UBGDX6FI.js +147 -0
  117. package/dist/chunk-UJILKYES.js +75 -0
  118. package/dist/chunk-ULMAQV3T.js +234 -0
  119. package/dist/chunk-VVBP6KGT.js +9 -0
  120. package/dist/chunk-W5VITDM2.js +168 -0
  121. package/dist/chunk-WAHQVNWT.js +194 -0
  122. package/dist/chunk-WBPVKEEE.js +227 -0
  123. package/dist/chunk-WGGYRBXH.js +5434 -0
  124. package/dist/chunk-WLYKU45P.js +5770 -0
  125. package/dist/chunk-WP7BVANJ.js +53 -0
  126. package/dist/chunk-WQQON3SV.js +473 -0
  127. package/dist/chunk-WQTOVMGV.js +119 -0
  128. package/dist/chunk-XBDP4QPW.js +7 -0
  129. package/dist/chunk-XEBHSJLT.js +103 -0
  130. package/dist/chunk-XF4VUMKW.js +231 -0
  131. package/dist/chunk-XQ2ECNK3.js +1 -0
  132. package/dist/chunk-Y5YXDM7D.js +74 -0
  133. package/dist/chunk-YOPAUVU4.js +2588 -0
  134. package/dist/chunk-YPT4A46F.js +362 -0
  135. package/dist/chunk-YWXT3JNM.js +425 -0
  136. package/dist/chunk-YY6APYUM.js +49 -0
  137. package/dist/chunk-Z2IJUO45.js +691 -0
  138. package/dist/chunk-ZHZ2Y2DH.js +1643 -0
  139. package/dist/chunk-ZT7JNIGX.js +152 -0
  140. package/dist/coordination/agent-monitor.d.ts +9 -1
  141. package/dist/coordination/agent-subagent-runner.d.ts +1 -1
  142. package/dist/coordination/agents/agent-prompts.d.ts +8 -0
  143. package/dist/coordination/agents/index.d.ts +33 -1
  144. package/dist/coordination/agents/index.js +184 -6269
  145. package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
  146. package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
  147. package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
  148. package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
  149. package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
  150. package/dist/coordination/agents/types.d.ts +13 -2
  151. package/dist/coordination/brain-monitor.d.ts +30 -7
  152. package/dist/coordination/collab-debug.d.ts +1 -1
  153. package/dist/coordination/collab-pause.d.ts +1 -1
  154. package/dist/coordination/delegate-tool.d.ts +7 -5
  155. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
  156. package/dist/coordination/director-options.d.ts +7 -0
  157. package/dist/coordination/director-spawn-model.d.ts +17 -0
  158. package/dist/coordination/director-tools.d.ts +2 -2
  159. package/dist/coordination/director.d.ts +12 -0
  160. package/dist/coordination/dispatcher.d.ts +8 -0
  161. package/dist/coordination/fleet-bus.d.ts +35 -4
  162. package/dist/coordination/fleet-supervisor.d.ts +10 -2
  163. package/dist/coordination/fleet.d.ts +16 -7
  164. package/dist/coordination/index.d.ts +4 -0
  165. package/dist/coordination/index.js +402 -34324
  166. package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
  167. package/dist/coordination/mailbox-http-router.d.ts +4 -3
  168. package/dist/coordination/mailbox-http-validation.d.ts +1 -1
  169. package/dist/coordination/mailbox-project-server.js +48 -3276
  170. package/dist/coordination/mailbox-types.d.ts +24 -6
  171. package/dist/coordination/model-tier-leader.d.ts +122 -0
  172. package/dist/coordination/model-tier.d.ts +122 -0
  173. package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
  174. package/dist/coordination/origin-session.d.ts +16 -0
  175. package/dist/coordination/provider-status-tracker.d.ts +41 -5
  176. package/dist/coordination/session-note-hub.d.ts +44 -0
  177. package/dist/coordination/session-note-tool.d.ts +10 -0
  178. package/dist/core/agent.d.ts +15 -2
  179. package/dist/core/context.d.ts +22 -3
  180. package/dist/core/index.d.ts +2 -0
  181. package/dist/core/index.js +239 -12360
  182. package/dist/core/model-ref.js +5 -23
  183. package/dist/core/provider-runner.d.ts +36 -1
  184. package/dist/core/request-conversation-binding.d.ts +37 -0
  185. package/dist/core/session-notes.d.ts +25 -0
  186. package/dist/core/system-prompt-builder.d.ts +11 -2
  187. package/dist/core/system-prompt-variants.d.ts +78 -0
  188. package/dist/design/index.js +50 -1087
  189. package/dist/execution/auto-compaction-middleware.d.ts +11 -0
  190. package/dist/execution/autonomy-brain.d.ts +2 -1
  191. package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
  192. package/dist/execution/compaction-core.d.ts +4 -4
  193. package/dist/execution/eternal-autonomy.d.ts +1 -1
  194. package/dist/execution/index.d.ts +1 -1
  195. package/dist/execution/index.js +1615 -21895
  196. package/dist/execution/model-runtime.d.ts +0 -7
  197. package/dist/execution/parallel-eternal-engine.d.ts +7 -7
  198. package/dist/execution/prompt-enhancer.js +16 -535
  199. package/dist/execution/retry-policy.d.ts +31 -1
  200. package/dist/extension/index.js +7 -311
  201. package/dist/goal/index.js +33 -3435
  202. package/dist/hooks/index.js +14 -1138
  203. package/dist/hq/index.js +114 -5139
  204. package/dist/hq/persistence/event-log.d.ts +0 -1
  205. package/dist/hq/persistence.d.ts +4 -4
  206. package/dist/hq/protocol.js +21 -668
  207. package/dist/hq/publisher.d.ts +2 -0
  208. package/dist/index.d.ts +14 -11
  209. package/dist/index.js +1502 -92655
  210. package/dist/infrastructure/index.d.ts +4 -4
  211. package/dist/infrastructure/index.js +504 -5230
  212. package/dist/infrastructure/token-counter.d.ts +9 -0
  213. package/dist/kernel/events/agent-events.d.ts +77 -3
  214. package/dist/kernel/events/brain-events.d.ts +12 -0
  215. package/dist/kernel/events/provider-events.d.ts +22 -1
  216. package/dist/kernel/events/session-events.d.ts +14 -0
  217. package/dist/kernel/events/tool-events.d.ts +13 -0
  218. package/dist/kernel/index.js +14 -992
  219. package/dist/mailbox-attach.d.ts +1 -1
  220. package/dist/models/index.js +41 -1117
  221. package/dist/notifications/index.js +3 -108
  222. package/dist/observability/event-bridge.d.ts +25 -5
  223. package/dist/observability/index.d.ts +9 -8
  224. package/dist/observability/index.js +477 -631
  225. package/dist/observability/tool-usage-source.d.ts +65 -0
  226. package/dist/plugin/index.d.ts +5 -0
  227. package/dist/plugin/index.js +600 -14835
  228. package/dist/plugins/auto-review-plugin.d.ts +3 -2
  229. package/dist/plugins/review-finding-commands.d.ts +0 -8
  230. package/dist/plugins/review-finding-store.js +22 -0
  231. package/dist/plugins/review-finding-types.js +19 -0
  232. package/dist/plugins/review-report-integration.js +21 -0
  233. package/dist/plugins/review-report-store.js +20 -0
  234. package/dist/plugins/review-report-types.js +13 -0
  235. package/dist/prompts/index.js +14 -948
  236. package/dist/registry/index.js +9 -822
  237. package/dist/registry/tool-registry.d.ts +59 -9
  238. package/dist/registry-7N4O3E7W.js +14 -0
  239. package/dist/replay/index.js +6 -84
  240. package/dist/security/capabilities.d.ts +6 -0
  241. package/dist/security/file-permissions.d.ts +1 -1
  242. package/dist/security/index.js +161 -2235
  243. package/dist/security/permission-policy.d.ts +12 -1
  244. package/dist/security/secret-scrubber.d.ts +15 -0
  245. package/dist/session-catalog/index.d.ts +1 -0
  246. package/dist/session-catalog/index.js +41 -2103
  247. package/dist/session-catalog/project-server.js +59 -1435
  248. package/dist/session-catalog/protocol.d.ts +32 -1
  249. package/dist/session-catalog/registry.d.ts +17 -1
  250. package/dist/session-catalog/session-agents.d.ts +56 -0
  251. package/dist/session-catalog/store-rebuild.d.ts +14 -0
  252. package/dist/session-catalog/store-schema.d.ts +26 -0
  253. package/dist/session-catalog/store.d.ts +27 -1
  254. package/dist/session-note-attach.d.ts +9 -0
  255. package/dist/skills/index.js +35 -1578
  256. package/dist/statusline/index.d.ts +43 -0
  257. package/dist/statusline/index.js +102 -0
  258. package/dist/storage/board-store-port.d.ts +2 -2
  259. package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
  260. package/dist/storage/config-loader.d.ts +3 -2
  261. package/dist/storage/file-session-writer.d.ts +4 -2
  262. package/dist/storage/goal-coordination.d.ts +6 -1
  263. package/dist/storage/goal-kanban.d.ts +4 -1
  264. package/dist/storage/index.d.ts +12 -4
  265. package/dist/storage/index.js +198 -15601
  266. package/dist/storage/input-history-store.d.ts +2 -4
  267. package/dist/storage/memory-curator.d.ts +133 -0
  268. package/dist/storage/session-agent-attribution.d.ts +39 -0
  269. package/dist/storage/session-doctor.d.ts +109 -0
  270. package/dist/storage/session-event-bridge.d.ts +5 -7
  271. package/dist/storage/session-outcome.d.ts +42 -0
  272. package/dist/storage/session-recovery.d.ts +55 -0
  273. package/dist/storage/session-store/load-session-data.d.ts +11 -0
  274. package/dist/storage/session-store/prune-helpers.d.ts +1 -1
  275. package/dist/storage/session-store/resume-session.d.ts +0 -2
  276. package/dist/storage/session-store/types.d.ts +8 -0
  277. package/dist/storage/session-store.d.ts +3 -3
  278. package/dist/storage/session-summary-tracker.d.ts +8 -1
  279. package/dist/storage/session-write-buffer.d.ts +18 -0
  280. package/dist/storage/session-writer-truncate.d.ts +2 -0
  281. package/dist/tasking/index.js +10 -676
  282. package/dist/tools/fallback-manage-tools.d.ts +6 -4
  283. package/dist/tools/index.js +52 -9590
  284. package/dist/tools/model-tier-set-tool.d.ts +66 -0
  285. package/dist/types/agent-bridge.d.ts +2 -1
  286. package/dist/types/config/autonomy.d.ts +5 -0
  287. package/dist/types/config/mcp-features.d.ts +7 -0
  288. package/dist/types/config/model-tiers.d.ts +148 -0
  289. package/dist/types/config/root.d.ts +9 -0
  290. package/dist/types/config/tools.d.ts +73 -0
  291. package/dist/types/config/ui.d.ts +1 -1
  292. package/dist/types/config.d.ts +1 -0
  293. package/dist/types/context.d.ts +2 -0
  294. package/dist/types/default-config.d.ts +6 -0
  295. package/dist/types/errors.d.ts +20 -1
  296. package/dist/types/index.d.ts +11 -9
  297. package/dist/types/index.js +131 -1342
  298. package/dist/types/multi-agent.d.ts +43 -0
  299. package/dist/types/plugin.d.ts +1 -1
  300. package/dist/types/provider-runner.d.ts +8 -0
  301. package/dist/types/provider.d.ts +15 -0
  302. package/dist/types/runtime-capability-manifest.d.ts +1 -1
  303. package/dist/types/secret-scrubber.d.ts +15 -0
  304. package/dist/types/session-markers.d.ts +37 -10
  305. package/dist/types/session-markers.js +8 -144
  306. package/dist/types/session-timeline.d.ts +197 -0
  307. package/dist/types/session-timeline.js +12 -0
  308. package/dist/types/session.d.ts +172 -11
  309. package/dist/types/system-prompt.d.ts +27 -0
  310. package/dist/utils/config-backup.d.ts +9 -1
  311. package/dist/utils/error.js +3 -4
  312. package/dist/utils/expect-defined.js +3 -9
  313. package/dist/utils/heap-watchdog.js +6 -706
  314. package/dist/utils/index.js +271 -5685
  315. package/dist/utils/message-invariants.d.ts +4 -1
  316. package/dist/utils/regex-guard.d.ts +1 -1
  317. package/dist/utils/sage-output-block.js +5 -42
  318. package/dist/utils/socket-path.d.ts +1 -1
  319. package/dist/utils/token-estimate.d.ts +6 -2
  320. package/dist/utils/wstack-paths.d.ts +2 -0
  321. package/dist/wiring/proxy-rewrite.d.ts +46 -0
  322. package/dist/wiring/proxy-rewrite.js +24 -121
  323. package/dist/worktree/index.js +6 -887
  324. package/instructions/agents/explore-companion.md +45 -18
  325. package/instructions/agents/memory-curator.md +34 -22
  326. package/instructions/coordination/director-preamble.md +12 -7
  327. package/instructions/coordination/subagent-baseline.md +26 -5
  328. package/instructions/leader-after-task.md +4 -1
  329. package/instructions/llm/chimera-review.md +30 -9
  330. package/instructions/llm/memory-curator.md +31 -0
  331. package/instructions/sections/tool/delegation-compact.md +2 -0
  332. package/instructions/sections/tool/delegation-full.md +5 -1
  333. package/instructions/sections/tool/mailbox-compact.md +5 -1
  334. package/instructions/sections/tool/mailbox-full.md +3 -0
  335. package/instructions/sections/tool/session-note-compact.md +3 -0
  336. package/instructions/sections/tool/session-note-full.md +15 -0
  337. package/instructions/system-lite.md +9 -4
  338. package/instructions/system-pro.md +66 -143
  339. package/instructions/system.md +73 -175
  340. package/package.json +32 -4
  341. package/dist/coordination/agents.d.ts +0 -8
  342. package/dist/coordination/transport.d.ts +0 -2
  343. package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
@@ -0,0 +1,900 @@
1
+ import {
2
+ assertNotPrivateHost
3
+ } from "./chunk-LA7CLDRW.js";
4
+ import {
5
+ buildChildEnv
6
+ } from "./chunk-6NWEVFEL.js";
7
+ import {
8
+ toErrorMessage
9
+ } from "./chunk-TBRL2RCC.js";
10
+
11
+ // src/hooks/shell-executor.ts
12
+ import { spawn } from "node:child_process";
13
+ var DEFAULT_TIMEOUT_MS = 5e3;
14
+ var MAX_OUTPUT_BYTES = 64 * 1024;
15
+ var ALLOWED_SHELL_COMMANDS = /* @__PURE__ */ new Set([
16
+ // POSIX shells + Windows shells
17
+ "bash",
18
+ "sh",
19
+ "dash",
20
+ "zsh",
21
+ "fish",
22
+ "pwsh",
23
+ "powershell",
24
+ "cmd",
25
+ // Script interpreters — hooks are routinely small node/python scripts
26
+ "node",
27
+ "deno",
28
+ "bun",
29
+ "npx",
30
+ "npm",
31
+ "pnpm",
32
+ "yarn",
33
+ "python",
34
+ "python3",
35
+ "perl",
36
+ "ruby",
37
+ // Utilities
38
+ "echo",
39
+ "cat",
40
+ "grep",
41
+ "sed",
42
+ "awk",
43
+ "find",
44
+ "sort",
45
+ "uniq",
46
+ "wc",
47
+ "head",
48
+ "tail",
49
+ "cut",
50
+ "tr",
51
+ "tee",
52
+ "xargs",
53
+ "printf",
54
+ "test",
55
+ "expr",
56
+ // File operations
57
+ "ls",
58
+ "stat",
59
+ "touch",
60
+ "mkdir",
61
+ "rm",
62
+ "cp",
63
+ "mv",
64
+ "chmod",
65
+ "chown",
66
+ // Network (read-only)
67
+ "curl",
68
+ "wget",
69
+ // Git (common operations)
70
+ "git",
71
+ "diff",
72
+ "merge",
73
+ // Process
74
+ "ps",
75
+ "kill",
76
+ "pgrep",
77
+ "pkill",
78
+ // Text processing
79
+ "jq",
80
+ "yq",
81
+ // System info
82
+ "uname",
83
+ "hostname",
84
+ "whoami",
85
+ "date"
86
+ ]);
87
+ function isAbsoluteCommandPath(p) {
88
+ return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
89
+ }
90
+ function isCommandAllowed(command) {
91
+ const argv = command.trim().split(/\s+/);
92
+ if (argv.length === 0 || argv[0].length === 0) return null;
93
+ const executable = argv[0];
94
+ if (isAbsoluteCommandPath(executable)) return argv;
95
+ const commandName = /[\\/]/.test(executable) ? executable.split(/[\\/]/).pop() ?? executable : executable;
96
+ if (ALLOWED_SHELL_COMMANDS.has(commandName)) return argv;
97
+ return null;
98
+ }
99
+ function killProcessTree(child) {
100
+ const pid = child.pid;
101
+ if (typeof pid === "number") {
102
+ if (process.platform === "win32") {
103
+ try {
104
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
105
+ stdio: "ignore",
106
+ windowsHide: true
107
+ });
108
+ killer.unref();
109
+ return;
110
+ } catch {
111
+ }
112
+ } else {
113
+ try {
114
+ process.kill(-pid, "SIGKILL");
115
+ return;
116
+ } catch {
117
+ }
118
+ }
119
+ }
120
+ try {
121
+ child.kill("SIGKILL");
122
+ } catch {
123
+ }
124
+ }
125
+ async function runShellHook(spec, input, logger) {
126
+ const result = await runShellHookDetailed(spec, input, logger);
127
+ if (result.outcome && "action" in result.outcome) {
128
+ if (result.outcome.action === "deny") {
129
+ return { decision: "block", reason: result.outcome.reason };
130
+ }
131
+ if (result.outcome.action === "mutate") {
132
+ return { decision: "allow", modifiedInput: result.outcome.input };
133
+ }
134
+ return { decision: "allow" };
135
+ }
136
+ return result.outcome;
137
+ }
138
+ async function runShellHookDetailed(spec, input, logger, options = {}) {
139
+ const timeoutMs = Math.max(1, Math.min(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS, 10 * 6e4));
140
+ const argv = isCommandAllowed(spec.command);
141
+ if (!argv) {
142
+ logger?.warn?.(`hook rejected: command not in allowlist: ${spec.command}`);
143
+ return {
144
+ outcome: null,
145
+ failure: { kind: "rejected", message: "command is not in the hook allowlist" }
146
+ };
147
+ }
148
+ return await new Promise((resolve) => {
149
+ let settled = false;
150
+ let timer;
151
+ const done = (v) => {
152
+ if (settled) return;
153
+ settled = true;
154
+ if (timer) clearTimeout(timer);
155
+ options.signal?.removeEventListener("abort", onAbort);
156
+ resolve(v);
157
+ };
158
+ let child;
159
+ try {
160
+ const cmd = argv[0];
161
+ const args = argv.slice(1);
162
+ child = spawn(cmd, args, {
163
+ cwd: input.cwd,
164
+ env: buildChildEnv(),
165
+ stdio: ["pipe", "pipe", "pipe"],
166
+ windowsHide: true,
167
+ detached: process.platform !== "win32"
168
+ });
169
+ } catch (err2) {
170
+ logger?.warn?.(`hook spawn failed: ${toErrorMessage(err2)}`);
171
+ return resolve({
172
+ outcome: null,
173
+ failure: { kind: "error", message: `spawn failed: ${toErrorMessage(err2)}` }
174
+ });
175
+ }
176
+ const onAbort = () => {
177
+ killProcessTree(child);
178
+ done({ outcome: null, failure: { kind: "aborted", message: "hook invocation aborted" } });
179
+ };
180
+ if (options.signal?.aborted) {
181
+ onAbort();
182
+ return;
183
+ }
184
+ options.signal?.addEventListener("abort", onAbort, { once: true });
185
+ timer = setTimeout(() => {
186
+ logger?.warn?.(`hook command timed out after ${timeoutMs}ms: ${spec.command}`);
187
+ killProcessTree(child);
188
+ done({
189
+ outcome: null,
190
+ failure: { kind: "timeout", message: `timed out after ${timeoutMs}ms` }
191
+ });
192
+ }, timeoutMs);
193
+ let out = "";
194
+ let err = "";
195
+ let outTrunc = false;
196
+ child.stdout?.on("data", (d) => {
197
+ if (outTrunc) return;
198
+ out += d.toString("utf8");
199
+ if (Buffer.byteLength(out, "utf8") > MAX_OUTPUT_BYTES) {
200
+ out = out.slice(0, MAX_OUTPUT_BYTES);
201
+ outTrunc = true;
202
+ }
203
+ });
204
+ child.stderr?.on("data", (d) => {
205
+ if (err.length < MAX_OUTPUT_BYTES) err += d.toString("utf8");
206
+ });
207
+ child.on("error", (e) => {
208
+ logger?.warn?.(`hook command error: ${e instanceof Error ? e.message : String(e)}`);
209
+ done({
210
+ outcome: null,
211
+ failure: { kind: "error", message: e instanceof Error ? e.message : String(e) }
212
+ });
213
+ });
214
+ child.on("close", (code) => {
215
+ if (code === 2) {
216
+ const reason = (err.trim() || out.trim() || "blocked by hook").slice(0, 2e3);
217
+ return done({ outcome: { action: "deny", reason } });
218
+ }
219
+ if (code !== 0) {
220
+ return done({
221
+ outcome: null,
222
+ failure: {
223
+ kind: "exit",
224
+ message: (err.trim() || `hook exited with code ${String(code)}`).slice(0, 2e3)
225
+ }
226
+ });
227
+ }
228
+ if (!out.trim()) return done({ outcome: null });
229
+ const parsed = parseHookOutcome(out);
230
+ if (!parsed) {
231
+ return done({
232
+ outcome: null,
233
+ failure: { kind: "invalid_output", message: "hook stdout was not valid outcome JSON" }
234
+ });
235
+ }
236
+ done({ outcome: parsed });
237
+ });
238
+ try {
239
+ child.stdin?.end(`${JSON.stringify(input)}
240
+ `);
241
+ } catch {
242
+ }
243
+ });
244
+ }
245
+ function parseHookOutcome(stdout) {
246
+ const trimmed = stdout.trim();
247
+ if (trimmed?.[0] !== "{") return null;
248
+ try {
249
+ const obj = JSON.parse(trimmed);
250
+ const outcome = {};
251
+ const common = {
252
+ ...typeof obj["additionalContext"] === "string" ? { additionalContext: obj["additionalContext"] } : {},
253
+ ...obj["contextAs"] === "inline" || obj["contextAs"] === "separate" ? { contextAs: obj["contextAs"] } : {}
254
+ };
255
+ if (obj["action"] === "allow" || obj["action"] === "deny" || obj["action"] === "mutate") {
256
+ if (obj["action"] === "deny") {
257
+ if (typeof obj["reason"] !== "string" || !obj["reason"].trim()) return null;
258
+ return { action: "deny", reason: obj["reason"], ...common };
259
+ }
260
+ if (obj["action"] === "mutate") {
261
+ if (!obj["input"] || typeof obj["input"] !== "object" || Array.isArray(obj["input"]))
262
+ return null;
263
+ return {
264
+ action: "mutate",
265
+ input: obj["input"],
266
+ ...typeof obj["reason"] === "string" ? { reason: obj["reason"] } : {},
267
+ ...common
268
+ };
269
+ }
270
+ return { action: "allow", ...common };
271
+ }
272
+ if (obj["decision"] === "block" || obj["decision"] === "allow") {
273
+ outcome.decision = obj["decision"];
274
+ }
275
+ if (typeof obj["reason"] === "string") outcome.reason = obj["reason"];
276
+ if (typeof obj["additionalContext"] === "string") {
277
+ outcome.additionalContext = obj["additionalContext"];
278
+ }
279
+ if (obj["modifiedInput"] && typeof obj["modifiedInput"] === "object") {
280
+ outcome.modifiedInput = obj["modifiedInput"];
281
+ }
282
+ if (obj["contextAs"] === "inline" || obj["contextAs"] === "separate") {
283
+ outcome.contextAs = obj["contextAs"];
284
+ }
285
+ return outcome;
286
+ } catch {
287
+ return null;
288
+ }
289
+ }
290
+
291
+ // src/hooks/http-executor.ts
292
+ var DEFAULT_TIMEOUT_MS2 = 5e3;
293
+ var MAX_OUTPUT_BYTES2 = 64 * 1024;
294
+ async function isAllowedUrl(raw) {
295
+ let url;
296
+ try {
297
+ url = new URL(raw);
298
+ } catch {
299
+ return false;
300
+ }
301
+ if (url.protocol === "https:") {
302
+ try {
303
+ await assertNotPrivateHost(url.hostname);
304
+ return true;
305
+ } catch {
306
+ return false;
307
+ }
308
+ }
309
+ if (url.protocol !== "http:") return false;
310
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
311
+ }
312
+ async function runHttpHookDetailed(spec, input, logger, options = {}) {
313
+ if (!await isAllowedUrl(spec.url)) {
314
+ return {
315
+ outcome: null,
316
+ failure: {
317
+ kind: "rejected",
318
+ message: "HTTP hooks require HTTPS, except for loopback HTTP endpoints"
319
+ }
320
+ };
321
+ }
322
+ const timeoutMs = Math.max(1, Math.min(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS2, 10 * 6e4));
323
+ const timeoutController = new AbortController();
324
+ const timer = setTimeout(() => timeoutController.abort(new Error("hook timeout")), timeoutMs);
325
+ timer.unref?.();
326
+ const signal = options.signal ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
327
+ try {
328
+ const response = await fetch(spec.url, {
329
+ method: "POST",
330
+ headers: { ...spec.headers ?? {}, "content-type": "application/json" },
331
+ body: JSON.stringify(input),
332
+ redirect: "error",
333
+ signal
334
+ });
335
+ if (!response.ok) {
336
+ return {
337
+ outcome: null,
338
+ failure: { kind: "error", message: `HTTP hook returned ${response.status}` }
339
+ };
340
+ }
341
+ const text = await response.text();
342
+ if (Buffer.byteLength(text, "utf8") > MAX_OUTPUT_BYTES2) {
343
+ return {
344
+ outcome: null,
345
+ failure: { kind: "invalid_output", message: "HTTP hook response exceeded 64 KiB" }
346
+ };
347
+ }
348
+ if (!text.trim()) return { outcome: null };
349
+ const outcome = parseHookOutcome(text);
350
+ return outcome ? { outcome } : {
351
+ outcome: null,
352
+ failure: {
353
+ kind: "invalid_output",
354
+ message: "HTTP hook response was not valid outcome JSON"
355
+ }
356
+ };
357
+ } catch (err) {
358
+ const timedOut = timeoutController.signal.aborted && !options.signal?.aborted;
359
+ const kind = timedOut ? "timeout" : options.signal?.aborted ? "aborted" : "error";
360
+ const message = timedOut ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
361
+ logger?.warn?.(`HTTP hook failed: ${message}`);
362
+ return { outcome: null, failure: { kind, message } };
363
+ } finally {
364
+ clearTimeout(timer);
365
+ }
366
+ }
367
+
368
+ // src/hooks/registry.ts
369
+ function entryControls(event, fallbackName, options, legacyStage) {
370
+ return {
371
+ name: options.name?.trim() || fallbackName,
372
+ timeoutMs: options.timeoutMs,
373
+ failurePolicy: options.failurePolicy ?? "open",
374
+ // Legacy PreToolUse hooks may rewrite input, so preserve their old
375
+ // behavior unless the registration explicitly opts into final validation.
376
+ stage: event === "PreToolUse" ? options.stage ?? legacyStage : "validate",
377
+ policy: options.policy === true,
378
+ // `background` only applies to PostToolUse — PreToolUse hooks always block.
379
+ background: event === "PostToolUse" && options.background === true
380
+ };
381
+ }
382
+ var HookRegistry = class _HookRegistry {
383
+ entries = [];
384
+ /** Register an in-process hook. Returns an unsubscribe function. */
385
+ registerInProcess(event, matcher, hook, owner, options = {}) {
386
+ const entry = {
387
+ kind: "inprocess",
388
+ event,
389
+ matcher: matcher ?? "*",
390
+ hook,
391
+ owner,
392
+ ...entryControls(
393
+ event,
394
+ owner ? `${owner}:${event}` : `inprocess:${event}`,
395
+ options,
396
+ "mutate"
397
+ )
398
+ };
399
+ this.entries.push(entry);
400
+ return () => this.remove(entry);
401
+ }
402
+ /**
403
+ * Register a single shell hook. The hook is owned by the runtime (no plugin
404
+ * name) — it survives plugin uninstalls. Returns an unsubscribe function.
405
+ */
406
+ registerShell(event, hook) {
407
+ const entry = {
408
+ kind: "shell",
409
+ event,
410
+ matcher: hook.matcher ?? "*",
411
+ command: hook.command,
412
+ ...entryControls(event, hook.command, hook, "validate")
413
+ };
414
+ this.entries.push(entry);
415
+ return () => this.remove(entry);
416
+ }
417
+ /** Register a native HTTP hook. */
418
+ registerHttp(event, hook) {
419
+ const entry = {
420
+ kind: "http",
421
+ event,
422
+ matcher: hook.matcher ?? "*",
423
+ url: hook.url,
424
+ headers: hook.headers,
425
+ ...entryControls(event, hook.url, hook, "validate")
426
+ };
427
+ this.entries.push(entry);
428
+ return () => this.remove(entry);
429
+ }
430
+ /** Bulk-load command/HTTP hooks from a `config.hooks` map. */
431
+ loadShellHooks(hooks, options = {}) {
432
+ if (!hooks) return;
433
+ for (const [event, list] of Object.entries(hooks)) {
434
+ for (const h of list ?? []) {
435
+ if (options.policyOnly && h.policy !== true) continue;
436
+ if (h.type === "http") {
437
+ if (h.url) this.registerHttp(event, h);
438
+ } else if (h.command) {
439
+ this.registerShell(event, h);
440
+ }
441
+ }
442
+ }
443
+ }
444
+ /**
445
+ * Atomically replace the shell-hook set: drop every currently-registered
446
+ * shell entry, then load the supplied `config.hooks` map. In-process
447
+ * hooks (and their plugin owners) are untouched — only shell entries
448
+ * are touched, because they are the only entries sourced from
449
+ * `config.hooks`.
450
+ *
451
+ * Designed for hot-reload via `ConfigStore.watch`: when the operator
452
+ * edits `config.json` and the store fires its watcher, the CLI calls
453
+ * this with the new `config.hooks` value. Idempotent — calling twice
454
+ * with the same map yields the same registry state (callers should still
455
+ * guard at the change-detection layer to avoid wasted work).
456
+ */
457
+ replaceShellHooks(hooks, options = {}) {
458
+ const replacement = new _HookRegistry();
459
+ replacement.loadShellHooks(hooks, options);
460
+ const configuredEntries = replacement.entries.slice();
461
+ for (let i = this.entries.length - 1; i >= 0; i--) {
462
+ const e = this.entries[i];
463
+ if (e && e.kind !== "inprocess") this.entries.splice(i, 1);
464
+ }
465
+ this.entries.push(...configuredEntries);
466
+ }
467
+ /** All entries registered for an event, in registration order. */
468
+ list(event) {
469
+ return this.entries.filter((e) => e.event === event);
470
+ }
471
+ /** True when any entry is registered for the event. */
472
+ has(event) {
473
+ return this.entries.some((e) => e.event === event);
474
+ }
475
+ /** Every entry currently registered (across all events). */
476
+ all() {
477
+ return this.entries.slice();
478
+ }
479
+ /**
480
+ * Drop every in-process hook whose `owner` matches. Used by the plugin
481
+ * loader during teardown as a belt-and-braces backstop for the per-call
482
+ * unsubscribe functions pushed onto `pluginCleanupFns` — if a plugin
483
+ * `setup()` throws partway through after registering some hooks, the
484
+ * remaining unsubscribes may never run, and the registry would otherwise
485
+ * hold dangling references to a torn-down plugin's closures.
486
+ *
487
+ * Returns the number of hooks actually removed (useful for tests).
488
+ * Shell hooks are owned by the runtime, not a plugin, so they're never
489
+ * drained by owner.
490
+ */
491
+ drainByOwner(owner) {
492
+ let removed = 0;
493
+ for (let i = this.entries.length - 1; i >= 0; i--) {
494
+ const e = this.entries[i];
495
+ if (e && e.kind === "inprocess" && e.owner === owner) {
496
+ this.entries.splice(i, 1);
497
+ removed++;
498
+ }
499
+ }
500
+ return removed;
501
+ }
502
+ /** Count of in-process hooks currently registered by `owner`. */
503
+ countByOwner(owner) {
504
+ let n = 0;
505
+ for (const e of this.entries) {
506
+ if (e.kind === "inprocess" && e.owner === owner) n++;
507
+ }
508
+ return n;
509
+ }
510
+ /** Drop every registered hook (used in teardown / tests). */
511
+ clear() {
512
+ this.entries.length = 0;
513
+ }
514
+ remove(entry) {
515
+ const i = this.entries.indexOf(entry);
516
+ if (i >= 0) this.entries.splice(i, 1);
517
+ }
518
+ };
519
+ function hookMatcherMatches(matcher, toolName) {
520
+ if (!matcher || matcher === "*") return true;
521
+ if (toolName === void 0) return true;
522
+ const target = toolName.toLowerCase();
523
+ return matcher.split("|").map((s) => s.trim().toLowerCase()).filter(Boolean).includes(target);
524
+ }
525
+
526
+ // src/hooks/runner.ts
527
+ var DEFAULT_TIMEOUT_MS3 = 5e3;
528
+ var DEFAULT_BACKGROUND_TIMEOUT_MS = 6e4;
529
+ var MAX_TIMEOUT_MS = 10 * 6e4;
530
+ function hookTimeoutMs(entry) {
531
+ return entry.timeoutMs ?? (entry.background ? DEFAULT_BACKGROUND_TIMEOUT_MS : DEFAULT_TIMEOUT_MS3);
532
+ }
533
+ function contextFromOutcome(outcome) {
534
+ return {
535
+ ...outcome.additionalContext ? { additionalContext: outcome.additionalContext } : {},
536
+ ...outcome.contextAs ? { contextAs: outcome.contextAs } : {}
537
+ };
538
+ }
539
+ function normalizePreToolOutcome(outcome) {
540
+ const context = contextFromOutcome(outcome);
541
+ if ("action" in outcome) return { ...outcome, ...context };
542
+ if (outcome.decision === "block") {
543
+ return {
544
+ action: "deny",
545
+ reason: outcome.reason?.trim() || "blocked by PreToolUse hook",
546
+ ...context
547
+ };
548
+ }
549
+ if (outcome.modifiedInput && typeof outcome.modifiedInput === "object") {
550
+ return {
551
+ action: "mutate",
552
+ input: outcome.modifiedInput,
553
+ ...outcome.reason ? { reason: outcome.reason } : {},
554
+ ...context
555
+ };
556
+ }
557
+ return { action: "allow", ...context };
558
+ }
559
+ function malformedOutcome(outcome) {
560
+ if (!("action" in outcome)) return void 0;
561
+ if (outcome.action === "allow") return void 0;
562
+ if (outcome.action === "deny") {
563
+ return typeof outcome.reason === "string" && outcome.reason.trim() ? void 0 : "deny outcome requires a non-empty reason";
564
+ }
565
+ if (outcome.action === "mutate") {
566
+ return outcome.input && typeof outcome.input === "object" && !Array.isArray(outcome.input) ? void 0 : "mutate outcome requires an object input";
567
+ }
568
+ return `unknown hook action: ${String(outcome.action)}`;
569
+ }
570
+ var HookRunner = class {
571
+ constructor(opts) {
572
+ this.opts = opts;
573
+ this.registry = opts.registry;
574
+ }
575
+ opts;
576
+ registry;
577
+ /**
578
+ * Background hooks are advisory and must never form an unbounded work queue.
579
+ * Keep at most one invocation running plus the latest pending payload for
580
+ * each registered hook.
581
+ */
582
+ backgroundRuns = /* @__PURE__ */ new Map();
583
+ /** Cheap guard so callers can skip building payloads when nothing listens. */
584
+ has(event) {
585
+ return this.registry.has(event);
586
+ }
587
+ async preToolUse(toolName, toolInput, env, toolMeta) {
588
+ const entries = this.matching("PreToolUse", toolName);
589
+ if (entries.length === 0) return {};
590
+ const declared = {
591
+ ...toolMeta?.capabilities ? { toolCapabilities: toolMeta.capabilities } : {},
592
+ ...toolMeta?.mutating === void 0 ? {} : { toolMutating: toolMeta.mutating }
593
+ };
594
+ let current = toolInput;
595
+ let mutated = false;
596
+ const contexts = [];
597
+ let contextAs;
598
+ const collectContext = (outcome) => {
599
+ if (outcome.additionalContext) contexts.push(outcome.additionalContext);
600
+ if (outcome.contextAs === "separate") contextAs = "separate";
601
+ else if (outcome.contextAs === "inline" && contextAs === void 0) contextAs = "inline";
602
+ };
603
+ const resultContext = () => ({
604
+ ...contexts.length > 0 ? { additionalContext: contexts.join("\n") } : {},
605
+ ...contextAs ? { contextAs } : {}
606
+ });
607
+ const mutators = entries.filter((entry) => entry.stage === "mutate");
608
+ const validators = entries.filter((entry) => entry.stage === "validate");
609
+ for (const entry of mutators) {
610
+ const payload = {
611
+ event: "PreToolUse",
612
+ toolName,
613
+ toolInput: current,
614
+ ...declared,
615
+ ...this.base(env)
616
+ };
617
+ const outcome = await this.invoke(entry, payload, env);
618
+ if (!outcome) continue;
619
+ const normalized = normalizePreToolOutcome(outcome);
620
+ collectContext(normalized);
621
+ if (normalized.action === "deny") {
622
+ return { block: true, reason: normalized.reason, ...resultContext() };
623
+ }
624
+ if (normalized.action === "mutate") {
625
+ current = normalized.input;
626
+ mutated = true;
627
+ }
628
+ }
629
+ for (const entry of validators) {
630
+ const payload = {
631
+ event: "PreToolUse",
632
+ toolName,
633
+ toolInput: current,
634
+ ...declared,
635
+ ...this.base(env)
636
+ };
637
+ const outcome = await this.invoke(entry, payload, env);
638
+ if (!outcome) continue;
639
+ const normalized = normalizePreToolOutcome(outcome);
640
+ collectContext(normalized);
641
+ if (normalized.action === "deny") {
642
+ return { block: true, reason: normalized.reason, ...resultContext() };
643
+ }
644
+ if (normalized.action === "mutate") {
645
+ const reason = `PreToolUse validator "${entry.name}" attempted to mutate tool arguments`;
646
+ this.opts.logger?.warn?.(reason);
647
+ if (entry.failurePolicy === "closed") {
648
+ return { block: true, reason, ...resultContext() };
649
+ }
650
+ }
651
+ }
652
+ return { ...mutated ? { input: current } : {}, ...resultContext() };
653
+ }
654
+ async postToolUse(toolName, toolInput, result, env) {
655
+ const payload = {
656
+ event: "PostToolUse",
657
+ toolName,
658
+ toolInput,
659
+ toolResult: result,
660
+ ...this.base(env)
661
+ };
662
+ const entries = this.matching("PostToolUse", toolName);
663
+ if (entries.length === 0) return {};
664
+ const foreground = [];
665
+ for (const entry of entries) {
666
+ if (entry.background) {
667
+ this.scheduleBackground(entry, payload, env);
668
+ } else {
669
+ foreground.push(entry);
670
+ }
671
+ }
672
+ if (foreground.length === 0) return {};
673
+ const results = await Promise.allSettled(
674
+ foreground.map((entry) => this.invoke(entry, payload, env))
675
+ );
676
+ const parts = [];
677
+ let separate = false;
678
+ for (const result2 of results) {
679
+ if (result2.status !== "fulfilled" || !result2.value?.additionalContext) continue;
680
+ parts.push(result2.value.additionalContext);
681
+ if (result2.value.contextAs === "separate") separate = true;
682
+ }
683
+ return parts.length ? { additionalContext: parts.join("\n"), contextAs: separate ? "separate" : "inline" } : {};
684
+ }
685
+ async userPromptSubmit(prompt, env) {
686
+ const entries = this.matching("UserPromptSubmit", void 0);
687
+ if (entries.length === 0) return {};
688
+ const payload = { event: "UserPromptSubmit", prompt, ...this.base(env) };
689
+ const parts = [];
690
+ for (const entry of entries) {
691
+ const outcome = await this.invoke(entry, payload, env);
692
+ if (!outcome) continue;
693
+ const denied = "action" in outcome ? outcome.action === "deny" : outcome.decision === "block";
694
+ if (denied) {
695
+ const reason = "reason" in outcome ? outcome.reason : void 0;
696
+ return { block: true, reason: reason ?? "Blocked by UserPromptSubmit hook" };
697
+ }
698
+ if (outcome.additionalContext) parts.push(outcome.additionalContext);
699
+ }
700
+ return parts.length ? { additionalContext: parts.join("\n") } : {};
701
+ }
702
+ async sessionStart(env) {
703
+ const payload = { event: "SessionStart", ...this.base(env) };
704
+ return {
705
+ additionalContext: await this.collectContext("SessionStart", void 0, payload, env)
706
+ };
707
+ }
708
+ async stop(env) {
709
+ const payload = { event: "Stop", ...this.base(env) };
710
+ await this.collectContext("Stop", void 0, payload, env);
711
+ }
712
+ // ── internals ──────────────────────────────────────────────────────
713
+ base(env) {
714
+ const sessionId = this.opts.sessionId?.();
715
+ return sessionId ? { cwd: env.cwd, sessionId } : { cwd: env.cwd };
716
+ }
717
+ matching(event, toolName) {
718
+ return this.registry.list(event).filter((e) => hookMatcherMatches(e.matcher, toolName));
719
+ }
720
+ scheduleBackground(entry, payload, env) {
721
+ const running = this.backgroundRuns.get(entry);
722
+ if (running !== void 0) {
723
+ running.pending = { payload, env };
724
+ return;
725
+ }
726
+ const state = { pending: null };
727
+ this.backgroundRuns.set(entry, state);
728
+ void (async () => {
729
+ let next = { payload, env };
730
+ while (next !== null) {
731
+ try {
732
+ await this.invoke(entry, next.payload, next.env);
733
+ } catch {
734
+ }
735
+ next = state.pending;
736
+ state.pending = null;
737
+ }
738
+ })().finally(() => {
739
+ this.backgroundRuns.delete(entry);
740
+ });
741
+ }
742
+ async invoke(entry, payload, env) {
743
+ const allowNonPolicy = this.opts.allowNonPolicy ?? true;
744
+ if (!allowNonPolicy && !entry.policy) return null;
745
+ let result;
746
+ if (entry.kind === "inprocess") {
747
+ result = await this.invokeInProcess(entry, payload, env);
748
+ } else {
749
+ result = entry.kind === "shell" ? await runShellHookDetailed(
750
+ { command: entry.command, timeoutMs: hookTimeoutMs(entry) },
751
+ payload,
752
+ this.opts.logger,
753
+ { signal: env.signal }
754
+ ) : await runHttpHookDetailed(
755
+ { url: entry.url, headers: entry.headers, timeoutMs: hookTimeoutMs(entry) },
756
+ payload,
757
+ this.opts.logger,
758
+ { signal: env.signal }
759
+ );
760
+ }
761
+ if (!result.failure && result.outcome) {
762
+ const malformed = malformedOutcome(result.outcome);
763
+ if (malformed) {
764
+ result = {
765
+ outcome: null,
766
+ failure: { kind: "invalid_output", message: malformed }
767
+ };
768
+ }
769
+ }
770
+ if (!result.failure) return result.outcome;
771
+ this.opts.logger?.warn?.(
772
+ `${payload.event} ${entry.background ? "background " : ""}hook "${entry.name}" failed (${result.failure.kind}): ${result.failure.message}`
773
+ );
774
+ return this.failureOutcome(entry, payload, result.failure);
775
+ }
776
+ async invokeInProcess(entry, payload, env) {
777
+ const timeoutMs = Math.max(1, Math.min(hookTimeoutMs(entry), MAX_TIMEOUT_MS));
778
+ const controller = new AbortController();
779
+ let timedOut = false;
780
+ const onParentAbort = () => controller.abort(env.signal?.reason);
781
+ if (env.signal?.aborted) {
782
+ return { outcome: null, failure: { kind: "aborted", message: "hook invocation aborted" } };
783
+ }
784
+ env.signal?.addEventListener("abort", onParentAbort, { once: true });
785
+ let timer;
786
+ const timeout = new Promise((_, reject) => {
787
+ timer = setTimeout(() => {
788
+ timedOut = true;
789
+ controller.abort(new Error("hook timeout"));
790
+ reject(new Error(`timed out after ${timeoutMs}ms`));
791
+ }, timeoutMs);
792
+ timer.unref?.();
793
+ });
794
+ const runtime = {
795
+ signal: controller.signal,
796
+ deadlineAt: Date.now() + timeoutMs
797
+ };
798
+ try {
799
+ const invocation = Promise.resolve().then(() => entry.hook(payload, runtime));
800
+ const outcome = await Promise.race([invocation, timeout]);
801
+ if (outcome === void 0) return { outcome: null };
802
+ if (!outcome || typeof outcome !== "object" || Array.isArray(outcome)) {
803
+ return {
804
+ outcome: null,
805
+ failure: {
806
+ kind: "invalid_output",
807
+ message: "in-process hook returned a non-object outcome"
808
+ }
809
+ };
810
+ }
811
+ return { outcome };
812
+ } catch (err) {
813
+ const aborted = env.signal?.aborted === true;
814
+ return {
815
+ outcome: null,
816
+ failure: {
817
+ kind: timedOut ? "timeout" : aborted ? "aborted" : "error",
818
+ message: toErrorMessage(err)
819
+ }
820
+ };
821
+ } finally {
822
+ if (timer) clearTimeout(timer);
823
+ env.signal?.removeEventListener("abort", onParentAbort);
824
+ }
825
+ }
826
+ failureOutcome(entry, payload, failure) {
827
+ if (entry.failurePolicy !== "closed") return null;
828
+ const reason = `${payload.event} policy hook "${entry.name}" failed (${failure.kind}); the action was denied by fail-closed policy. This is not an approval request.`;
829
+ if (payload.event === "PreToolUse") return { action: "deny", reason };
830
+ if (payload.event === "UserPromptSubmit") return { decision: "block", reason };
831
+ return null;
832
+ }
833
+ async collectContext(event, toolName, payload, env) {
834
+ const entries = this.matching(event, toolName);
835
+ if (entries.length === 0) return void 0;
836
+ const results = await Promise.allSettled(
837
+ entries.map((entry) => this.invoke(entry, payload, env))
838
+ );
839
+ const parts = [];
840
+ for (const result of results) {
841
+ if (result.status === "fulfilled" && result.value?.additionalContext) {
842
+ parts.push(result.value.additionalContext);
843
+ }
844
+ }
845
+ return parts.length ? parts.join("\n") : void 0;
846
+ }
847
+ };
848
+
849
+ // src/hooks/shell-hooks-equal.ts
850
+ function stableJson(value) {
851
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
852
+ if (value && typeof value === "object") {
853
+ const entries = Object.entries(value).filter(([, child]) => child !== void 0).sort(([a], [b]) => a.localeCompare(b));
854
+ return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
855
+ }
856
+ return JSON.stringify(value) ?? "null";
857
+ }
858
+ function shellHooksEqual(a, b) {
859
+ if (a === b) return true;
860
+ if (!a || !b) return !a && !b;
861
+ const aKeys = Object.keys(a);
862
+ const bKeys = Object.keys(b);
863
+ if (aKeys.length !== bKeys.length) return false;
864
+ for (const k of aKeys) {
865
+ const event = k;
866
+ const aList = a[event];
867
+ const bList = b[event];
868
+ if (aList === bList) continue;
869
+ if (!aList || !bList) return false;
870
+ if (aList.length !== bList.length) return false;
871
+ for (let i = 0; i < aList.length; i++) {
872
+ const x = aList[i];
873
+ const y = bList[i];
874
+ if (!x || !y) return !x && !y;
875
+ if (stableJson(x) !== stableJson(y)) return false;
876
+ }
877
+ }
878
+ return true;
879
+ }
880
+ function countShellHooks(hooks) {
881
+ if (!hooks) return 0;
882
+ let n = 0;
883
+ for (const list of Object.values(hooks)) {
884
+ if (list) n += list.length;
885
+ }
886
+ return n;
887
+ }
888
+
889
+ export {
890
+ runShellHook,
891
+ runShellHookDetailed,
892
+ parseHookOutcome,
893
+ runHttpHookDetailed,
894
+ HookRegistry,
895
+ hookMatcherMatches,
896
+ HookRunner,
897
+ shellHooksEqual,
898
+ countShellHooks
899
+ };
900
+ //# sourceMappingURL=chunk-TWJMGXCI.js.map