@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
@@ -1,1141 +1,17 @@
1
- // src/utils/ip-guard.ts
2
- import * as dns from "node:dns/promises";
3
- import * as net from "node:net";
4
- function isPrivateIPv4(addr) {
5
- const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
6
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
7
- return true;
8
- }
9
- const [a, b, c] = parts;
10
- if (a === 0) return true;
11
- if (a === 10) return true;
12
- if (a === 127) return true;
13
- if (a === 169 && b === 254) return true;
14
- if (a === 172 && b >= 16 && b <= 31) return true;
15
- if (a === 192 && b === 168) return true;
16
- if (a === 192 && b === 0 && c === 0) return true;
17
- if (a === 100 && b >= 64 && b <= 127) return true;
18
- if (a >= 224) return true;
19
- return false;
20
- }
21
- function isPrivateIPv6(raw) {
22
- const lower = raw.toLowerCase();
23
- if (lower === "::" || lower === "::1") return true;
24
- const groups = expandIPv6(lower);
25
- if (!groups) return true;
26
- if (groups[0] === 100 && groups[1] === 65435 && groups[2] === 1) return true;
27
- if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
28
- const a = (groups[6] ?? 0) >> 8;
29
- const b = (groups[6] ?? 0) & 255;
30
- const c = (groups[7] ?? 0) >> 8;
31
- const d = (groups[7] ?? 0) & 255;
32
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
33
- }
34
- const embedded = embeddedIPv4(groups);
35
- if (embedded) return isPrivateIPv4(embedded);
36
- const high = groups[0] ?? 0;
37
- if ((high & 65024) === 64512) return true;
38
- if ((high & 65472) === 65152) return true;
39
- if ((high & 65280) === 65280) return true;
40
- return false;
41
- }
42
- function quad(hi, lo) {
43
- return `${(hi ?? 0) >> 8}.${(hi ?? 0) & 255}.${(lo ?? 0) >> 8}.${(lo ?? 0) & 255}`;
44
- }
45
- function embeddedIPv4(groups) {
46
- const g = (i) => groups[i] ?? 0;
47
- if (g(0) === 100 && g(1) === 65435) {
48
- if (g(2) === 0 && g(3) === 0 && g(4) === 0 && g(5) === 0) {
49
- return quad(g(6), g(7));
50
- }
51
- }
52
- if (g(0) === 8194) return quad(g(1), g(2));
53
- if (g(0) === 8193 && g(1) === 0) {
54
- return quad(~g(6) & 65535, ~g(7) & 65535);
55
- }
56
- if ((g(4) === 0 || g(4) === 512) && g(5) === 24318) {
57
- return quad(g(6), g(7));
58
- }
59
- const highZero = g(0) === 0 && g(1) === 0 && g(2) === 0 && g(3) === 0;
60
- if (highZero && g(4) === 65535 && g(5) === 0) return quad(g(6), g(7));
61
- if (highZero && g(4) === 0 && g(5) === 0) return quad(g(6), g(7));
62
- return void 0;
63
- }
64
- function expandIPv6(addr) {
65
- const parts = addr.split("::");
66
- if (parts.length > 2) return null;
67
- const parseGroups = (s) => {
68
- if (s === "") return [];
69
- const out = [];
70
- for (const g of s.split(":")) {
71
- if (g.length === 0 || g.length > 4) return null;
72
- const n = Number.parseInt(g, 16);
73
- if (Number.isNaN(n) || n < 0 || n > 65535) return null;
74
- out.push(n);
75
- }
76
- return out;
77
- };
78
- if (parts.length === 1) {
79
- const groups = parseGroups(parts[0] ?? "");
80
- if (groups?.length !== 8) return null;
81
- return groups;
82
- }
83
- const head = parseGroups(parts[0] ?? "");
84
- const tail = parseGroups(parts[1] ?? "");
85
- if (!head || !tail) return null;
86
- const fill = 8 - head.length - tail.length;
87
- if (fill < 0) return null;
88
- return [...head, ...new Array(fill).fill(0), ...tail];
89
- }
90
- async function assertNotPrivateHost(hostname) {
91
- const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
92
- if (host === "localhost" || host.endsWith(".localhost")) {
93
- throw new Error("fetch: blocked localhost target");
94
- }
95
- const ipVersion = net.isIP(host);
96
- if (ipVersion === 4) {
97
- if (isPrivateIPv4(host)) {
98
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
99
- }
100
- } else if (ipVersion === 6) {
101
- if (isPrivateIPv6(host)) {
102
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
103
- }
104
- } else {
105
- try {
106
- const records = await dns.lookup(host, { all: true });
107
- for (const r of records) {
108
- const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
109
- if (bad) {
110
- throw new Error(`fetch: resolved to private address ${r.address}`);
111
- }
112
- }
113
- } catch (err) {
114
- if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
115
- }
116
- }
117
- }
118
-
119
- // src/hooks/shell-executor.ts
120
- import { spawn } from "node:child_process";
121
-
122
- // src/utils/child-env.ts
123
- var ALLOWED_KEYS = /* @__PURE__ */ new Set([
124
- "PATH",
125
- "HOME",
126
- "USER",
127
- "USERNAME",
128
- "LOGNAME",
129
- "SHELL",
130
- "LANG",
131
- "LC_ALL",
132
- "LC_CTYPE",
133
- "TERM",
134
- "TZ",
135
- "TMPDIR",
136
- "TEMP",
137
- "TMP",
138
- "PWD",
139
- "OLDPWD",
140
- "COMSPEC",
141
- "SYSTEMROOT",
142
- "SYSTEMDRIVE",
143
- "WINDIR",
144
- "PROGRAMFILES",
145
- "PROGRAMFILES(X86)",
146
- "PROGRAMDATA",
147
- "APPDATA",
148
- "LOCALAPPDATA",
149
- "USERPROFILE",
150
- "PUBLIC",
151
- "PATHEXT"
152
- ]);
153
- var SECRET_NAME_PARTS = [
154
- "TOKEN",
155
- "SECRET",
156
- "PASSWORD",
157
- "PASSWD",
158
- // WRONGSTACK_VAULT_PASSPHRASE is the vault KEK. Without this entry it matched
159
- // no rule here and fell through to the `WRONGSTACK_` prefix allow-rule, so the
160
- // key that protects the credential vault was forwarded to every child process
161
- // — including the agent's own exec/bash tools and MCP stdio servers (WS-033).
162
- "PASSPHRASE",
163
- "AUTH",
164
- "CRED",
165
- "BEARER",
166
- "COOKIE",
167
- "PRIVATE"
168
- ];
169
- function looksSecret(name) {
170
- const upper = name.toUpperCase();
171
- for (const p of SECRET_NAME_PARTS) {
172
- if (upper.includes(p)) return true;
173
- }
174
- if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
175
- if (/API[_-]?KEY/i.test(upper)) return true;
176
- if (/ACCESS[_-]?KEY/i.test(upper)) return true;
177
- if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
178
- return true;
179
- }
180
- return false;
181
- }
182
- function valueHasEmbeddedCredential(value) {
183
- return /\b[a-z][a-z0-9+.-]*:\/\/[^/\s:@]*:[^/\s@]+@/i.test(value);
184
- }
185
- var NODE_OPTIONS_INJECTION_FLAG = /^(?:--require|-r|--import|--loader|--experimental-loader)$/;
186
- var NODE_OPTIONS_INJECTION_FLAG_EQ = /^(?:--require|-r|--import|--loader|--experimental-loader)=/;
187
- function sanitizeNodeOptions(value) {
188
- const tokens = value.split(/\s+/).filter(Boolean);
189
- const kept = [];
190
- for (let i = 0; i < tokens.length; i++) {
191
- const tok = tokens[i];
192
- if (NODE_OPTIONS_INJECTION_FLAG_EQ.test(tok)) continue;
193
- if (NODE_OPTIONS_INJECTION_FLAG.test(tok)) {
194
- i++;
195
- continue;
196
- }
197
- kept.push(tok);
198
- }
199
- return kept.join(" ");
200
- }
201
- var gitIdentity = null;
202
- function buildChildEnv(optsOrSessionId) {
203
- const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
204
- const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
205
- const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
206
- const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
207
- if (passthrough && !process.env["CI"]) {
208
- console.warn(
209
- "[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
210
- );
211
- }
212
- const out = {};
213
- const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
214
- for (const [k, v] of Object.entries(process.env)) {
215
- if (v === void 0) continue;
216
- if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
217
- if (passthrough) {
218
- out[k] = v;
219
- continue;
220
- }
221
- const upper = k.toUpperCase();
222
- if (valueHasEmbeddedCredential(v)) continue;
223
- if (ALLOWED_KEYS.has(upper)) {
224
- out[k] = v;
225
- continue;
226
- }
227
- if (looksSecret(upper)) continue;
228
- if (upper === "NODE_OPTIONS") {
229
- const sanitized = sanitizeNodeOptions(v);
230
- if (sanitized) out[k] = sanitized;
231
- continue;
232
- }
233
- if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
234
- // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
235
- // vault). Forwarding keeps child wstack processes — e.g. ones spawned
236
- // by the test suite — inside the same redirected global root.
237
- upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
238
- out[k] = v;
239
- }
240
- }
241
- if (gitIdentity) {
242
- if (gitIdentity.name) {
243
- out["GIT_AUTHOR_NAME"] = gitIdentity.name;
244
- out["GIT_COMMITTER_NAME"] = gitIdentity.name;
245
- }
246
- if (gitIdentity.email) {
247
- out["GIT_AUTHOR_EMAIL"] = gitIdentity.email;
248
- out["GIT_COMMITTER_EMAIL"] = gitIdentity.email;
249
- }
250
- }
251
- if (opts.extra) {
252
- Object.assign(out, opts.extra);
253
- }
254
- if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
255
- return out;
256
- }
257
-
258
- // src/utils/error.ts
259
- function toErrorMessage(err) {
260
- return err instanceof Error ? err.message : String(err);
261
- }
262
-
263
- // src/hooks/shell-executor.ts
264
- var DEFAULT_TIMEOUT_MS = 5e3;
265
- var MAX_OUTPUT_BYTES = 64 * 1024;
266
- var ALLOWED_SHELL_COMMANDS = /* @__PURE__ */ new Set([
267
- // POSIX shells + Windows shells
268
- "bash",
269
- "sh",
270
- "dash",
271
- "zsh",
272
- "fish",
273
- "pwsh",
274
- "powershell",
275
- "cmd",
276
- // Script interpreters — hooks are routinely small node/python scripts
277
- "node",
278
- "deno",
279
- "bun",
280
- "npx",
281
- "npm",
282
- "pnpm",
283
- "yarn",
284
- "python",
285
- "python3",
286
- "perl",
287
- "ruby",
288
- // Utilities
289
- "echo",
290
- "cat",
291
- "grep",
292
- "sed",
293
- "awk",
294
- "find",
295
- "sort",
296
- "uniq",
297
- "wc",
298
- "head",
299
- "tail",
300
- "cut",
301
- "tr",
302
- "tee",
303
- "xargs",
304
- "printf",
305
- "test",
306
- "expr",
307
- // File operations
308
- "ls",
309
- "stat",
310
- "touch",
311
- "mkdir",
312
- "rm",
313
- "cp",
314
- "mv",
315
- "chmod",
316
- "chown",
317
- // Network (read-only)
318
- "curl",
319
- "wget",
320
- // Git (common operations)
321
- "git",
322
- "diff",
323
- "merge",
324
- // Process
325
- "ps",
326
- "kill",
327
- "pgrep",
328
- "pkill",
329
- // Text processing
330
- "jq",
331
- "yq",
332
- // System info
333
- "uname",
334
- "hostname",
335
- "whoami",
336
- "date"
337
- ]);
338
- function isAbsoluteCommandPath(p) {
339
- return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
340
- }
341
- function isCommandAllowed(command) {
342
- const argv = command.trim().split(/\s+/);
343
- if (argv.length === 0 || argv[0].length === 0) return null;
344
- const executable = argv[0];
345
- if (isAbsoluteCommandPath(executable)) return argv;
346
- const commandName = /[\\/]/.test(executable) ? executable.split(/[\\/]/).pop() ?? executable : executable;
347
- if (ALLOWED_SHELL_COMMANDS.has(commandName)) return argv;
348
- return null;
349
- }
350
- function killProcessTree(child) {
351
- const pid = child.pid;
352
- if (typeof pid === "number") {
353
- if (process.platform === "win32") {
354
- try {
355
- const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
356
- stdio: "ignore",
357
- windowsHide: true
358
- });
359
- killer.unref();
360
- return;
361
- } catch {
362
- }
363
- } else {
364
- try {
365
- process.kill(-pid, "SIGKILL");
366
- return;
367
- } catch {
368
- }
369
- }
370
- }
371
- try {
372
- child.kill("SIGKILL");
373
- } catch {
374
- }
375
- }
376
- async function runShellHook(spec, input, logger) {
377
- const result = await runShellHookDetailed(spec, input, logger);
378
- if (result.outcome && "action" in result.outcome) {
379
- if (result.outcome.action === "deny") {
380
- return { decision: "block", reason: result.outcome.reason };
381
- }
382
- if (result.outcome.action === "mutate") {
383
- return { decision: "allow", modifiedInput: result.outcome.input };
384
- }
385
- return { decision: "allow" };
386
- }
387
- return result.outcome;
388
- }
389
- async function runShellHookDetailed(spec, input, logger, options = {}) {
390
- const timeoutMs = Math.max(1, Math.min(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS, 10 * 6e4));
391
- const argv = isCommandAllowed(spec.command);
392
- if (!argv) {
393
- logger?.warn?.(`hook rejected: command not in allowlist: ${spec.command}`);
394
- return {
395
- outcome: null,
396
- failure: { kind: "rejected", message: "command is not in the hook allowlist" }
397
- };
398
- }
399
- return await new Promise((resolve) => {
400
- let settled = false;
401
- let timer;
402
- const done = (v) => {
403
- if (settled) return;
404
- settled = true;
405
- if (timer) clearTimeout(timer);
406
- options.signal?.removeEventListener("abort", onAbort);
407
- resolve(v);
408
- };
409
- let child;
410
- try {
411
- const cmd = argv[0];
412
- const args = argv.slice(1);
413
- child = spawn(cmd, args, {
414
- cwd: input.cwd,
415
- env: buildChildEnv(),
416
- stdio: ["pipe", "pipe", "pipe"],
417
- windowsHide: true,
418
- detached: process.platform !== "win32"
419
- });
420
- } catch (err2) {
421
- logger?.warn?.(`hook spawn failed: ${toErrorMessage(err2)}`);
422
- return resolve({
423
- outcome: null,
424
- failure: { kind: "error", message: `spawn failed: ${toErrorMessage(err2)}` }
425
- });
426
- }
427
- const onAbort = () => {
428
- killProcessTree(child);
429
- done({ outcome: null, failure: { kind: "aborted", message: "hook invocation aborted" } });
430
- };
431
- if (options.signal?.aborted) {
432
- onAbort();
433
- return;
434
- }
435
- options.signal?.addEventListener("abort", onAbort, { once: true });
436
- timer = setTimeout(() => {
437
- logger?.warn?.(`hook command timed out after ${timeoutMs}ms: ${spec.command}`);
438
- killProcessTree(child);
439
- done({
440
- outcome: null,
441
- failure: { kind: "timeout", message: `timed out after ${timeoutMs}ms` }
442
- });
443
- }, timeoutMs);
444
- let out = "";
445
- let err = "";
446
- let outTrunc = false;
447
- child.stdout?.on("data", (d) => {
448
- if (outTrunc) return;
449
- out += d.toString("utf8");
450
- if (Buffer.byteLength(out, "utf8") > MAX_OUTPUT_BYTES) {
451
- out = out.slice(0, MAX_OUTPUT_BYTES);
452
- outTrunc = true;
453
- }
454
- });
455
- child.stderr?.on("data", (d) => {
456
- if (err.length < MAX_OUTPUT_BYTES) err += d.toString("utf8");
457
- });
458
- child.on("error", (e) => {
459
- logger?.warn?.(`hook command error: ${e instanceof Error ? e.message : String(e)}`);
460
- done({
461
- outcome: null,
462
- failure: { kind: "error", message: e instanceof Error ? e.message : String(e) }
463
- });
464
- });
465
- child.on("close", (code) => {
466
- if (code === 2) {
467
- const reason = (err.trim() || out.trim() || "blocked by hook").slice(0, 2e3);
468
- return done({ outcome: { action: "deny", reason } });
469
- }
470
- if (code !== 0) {
471
- return done({
472
- outcome: null,
473
- failure: {
474
- kind: "exit",
475
- message: (err.trim() || `hook exited with code ${String(code)}`).slice(0, 2e3)
476
- }
477
- });
478
- }
479
- if (!out.trim()) return done({ outcome: null });
480
- const parsed = parseHookOutcome(out);
481
- if (!parsed) {
482
- return done({
483
- outcome: null,
484
- failure: { kind: "invalid_output", message: "hook stdout was not valid outcome JSON" }
485
- });
486
- }
487
- done({ outcome: parsed });
488
- });
489
- try {
490
- child.stdin?.end(`${JSON.stringify(input)}
491
- `);
492
- } catch {
493
- }
494
- });
495
- }
496
- function parseHookOutcome(stdout) {
497
- const trimmed = stdout.trim();
498
- if (trimmed?.[0] !== "{") return null;
499
- try {
500
- const obj = JSON.parse(trimmed);
501
- const outcome = {};
502
- const common = {
503
- ...typeof obj["additionalContext"] === "string" ? { additionalContext: obj["additionalContext"] } : {},
504
- ...obj["contextAs"] === "inline" || obj["contextAs"] === "separate" ? { contextAs: obj["contextAs"] } : {}
505
- };
506
- if (obj["action"] === "allow" || obj["action"] === "deny" || obj["action"] === "mutate") {
507
- if (obj["action"] === "deny") {
508
- if (typeof obj["reason"] !== "string" || !obj["reason"].trim()) return null;
509
- return { action: "deny", reason: obj["reason"], ...common };
510
- }
511
- if (obj["action"] === "mutate") {
512
- if (!obj["input"] || typeof obj["input"] !== "object" || Array.isArray(obj["input"]))
513
- return null;
514
- return {
515
- action: "mutate",
516
- input: obj["input"],
517
- ...typeof obj["reason"] === "string" ? { reason: obj["reason"] } : {},
518
- ...common
519
- };
520
- }
521
- return { action: "allow", ...common };
522
- }
523
- if (obj["decision"] === "block" || obj["decision"] === "allow") {
524
- outcome.decision = obj["decision"];
525
- }
526
- if (typeof obj["reason"] === "string") outcome.reason = obj["reason"];
527
- if (typeof obj["additionalContext"] === "string") {
528
- outcome.additionalContext = obj["additionalContext"];
529
- }
530
- if (obj["modifiedInput"] && typeof obj["modifiedInput"] === "object") {
531
- outcome.modifiedInput = obj["modifiedInput"];
532
- }
533
- if (obj["contextAs"] === "inline" || obj["contextAs"] === "separate") {
534
- outcome.contextAs = obj["contextAs"];
535
- }
536
- return outcome;
537
- } catch {
538
- return null;
539
- }
540
- }
541
-
542
- // src/hooks/http-executor.ts
543
- var DEFAULT_TIMEOUT_MS2 = 5e3;
544
- var MAX_OUTPUT_BYTES2 = 64 * 1024;
545
- async function isAllowedUrl(raw) {
546
- let url;
547
- try {
548
- url = new URL(raw);
549
- } catch {
550
- return false;
551
- }
552
- if (url.protocol === "https:") {
553
- try {
554
- await assertNotPrivateHost(url.hostname);
555
- return true;
556
- } catch {
557
- return false;
558
- }
559
- }
560
- if (url.protocol !== "http:") return false;
561
- return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
562
- }
563
- async function runHttpHookDetailed(spec, input, logger, options = {}) {
564
- if (!await isAllowedUrl(spec.url)) {
565
- return {
566
- outcome: null,
567
- failure: {
568
- kind: "rejected",
569
- message: "HTTP hooks require HTTPS, except for loopback HTTP endpoints"
570
- }
571
- };
572
- }
573
- const timeoutMs = Math.max(1, Math.min(spec.timeoutMs ?? DEFAULT_TIMEOUT_MS2, 10 * 6e4));
574
- const timeoutController = new AbortController();
575
- const timer = setTimeout(() => timeoutController.abort(new Error("hook timeout")), timeoutMs);
576
- timer.unref?.();
577
- const signal = options.signal ? AbortSignal.any([options.signal, timeoutController.signal]) : timeoutController.signal;
578
- try {
579
- const response = await fetch(spec.url, {
580
- method: "POST",
581
- headers: { ...spec.headers ?? {}, "content-type": "application/json" },
582
- body: JSON.stringify(input),
583
- redirect: "error",
584
- signal
585
- });
586
- if (!response.ok) {
587
- return {
588
- outcome: null,
589
- failure: { kind: "error", message: `HTTP hook returned ${response.status}` }
590
- };
591
- }
592
- const text = await response.text();
593
- if (Buffer.byteLength(text, "utf8") > MAX_OUTPUT_BYTES2) {
594
- return {
595
- outcome: null,
596
- failure: { kind: "invalid_output", message: "HTTP hook response exceeded 64 KiB" }
597
- };
598
- }
599
- if (!text.trim()) return { outcome: null };
600
- const outcome = parseHookOutcome(text);
601
- return outcome ? { outcome } : {
602
- outcome: null,
603
- failure: {
604
- kind: "invalid_output",
605
- message: "HTTP hook response was not valid outcome JSON"
606
- }
607
- };
608
- } catch (err) {
609
- const timedOut = timeoutController.signal.aborted && !options.signal?.aborted;
610
- const kind = timedOut ? "timeout" : options.signal?.aborted ? "aborted" : "error";
611
- const message = timedOut ? `timed out after ${timeoutMs}ms` : err instanceof Error ? err.message : String(err);
612
- logger?.warn?.(`HTTP hook failed: ${message}`);
613
- return { outcome: null, failure: { kind, message } };
614
- } finally {
615
- clearTimeout(timer);
616
- }
617
- }
618
-
619
- // src/hooks/registry.ts
620
- function entryControls(event, fallbackName, options, legacyStage) {
621
- return {
622
- name: options.name?.trim() || fallbackName,
623
- timeoutMs: options.timeoutMs,
624
- failurePolicy: options.failurePolicy ?? "open",
625
- // Legacy PreToolUse hooks may rewrite input, so preserve their old
626
- // behavior unless the registration explicitly opts into final validation.
627
- stage: event === "PreToolUse" ? options.stage ?? legacyStage : "validate",
628
- policy: options.policy === true,
629
- // `background` only applies to PostToolUse — PreToolUse hooks always block.
630
- background: event === "PostToolUse" && options.background === true
631
- };
632
- }
633
- var HookRegistry = class _HookRegistry {
634
- entries = [];
635
- /** Register an in-process hook. Returns an unsubscribe function. */
636
- registerInProcess(event, matcher, hook, owner, options = {}) {
637
- const entry = {
638
- kind: "inprocess",
639
- event,
640
- matcher: matcher ?? "*",
641
- hook,
642
- owner,
643
- ...entryControls(
644
- event,
645
- owner ? `${owner}:${event}` : `inprocess:${event}`,
646
- options,
647
- "mutate"
648
- )
649
- };
650
- this.entries.push(entry);
651
- return () => this.remove(entry);
652
- }
653
- /**
654
- * Register a single shell hook. The hook is owned by the runtime (no plugin
655
- * name) — it survives plugin uninstalls. Returns an unsubscribe function.
656
- */
657
- registerShell(event, hook) {
658
- const entry = {
659
- kind: "shell",
660
- event,
661
- matcher: hook.matcher ?? "*",
662
- command: hook.command,
663
- ...entryControls(event, hook.command, hook, "validate")
664
- };
665
- this.entries.push(entry);
666
- return () => this.remove(entry);
667
- }
668
- /** Register a native HTTP hook. */
669
- registerHttp(event, hook) {
670
- const entry = {
671
- kind: "http",
672
- event,
673
- matcher: hook.matcher ?? "*",
674
- url: hook.url,
675
- headers: hook.headers,
676
- ...entryControls(event, hook.url, hook, "validate")
677
- };
678
- this.entries.push(entry);
679
- return () => this.remove(entry);
680
- }
681
- /** Bulk-load command/HTTP hooks from a `config.hooks` map. */
682
- loadShellHooks(hooks, options = {}) {
683
- if (!hooks) return;
684
- for (const [event, list] of Object.entries(hooks)) {
685
- for (const h of list ?? []) {
686
- if (options.policyOnly && h.policy !== true) continue;
687
- if (h.type === "http") {
688
- if (h.url) this.registerHttp(event, h);
689
- } else if (h.command) {
690
- this.registerShell(event, h);
691
- }
692
- }
693
- }
694
- }
695
- /**
696
- * Atomically replace the shell-hook set: drop every currently-registered
697
- * shell entry, then load the supplied `config.hooks` map. In-process
698
- * hooks (and their plugin owners) are untouched — only shell entries
699
- * are touched, because they are the only entries sourced from
700
- * `config.hooks`.
701
- *
702
- * Designed for hot-reload via `ConfigStore.watch`: when the operator
703
- * edits `config.json` and the store fires its watcher, the CLI calls
704
- * this with the new `config.hooks` value. Idempotent — calling twice
705
- * with the same map yields the same registry state (callers should still
706
- * guard at the change-detection layer to avoid wasted work).
707
- */
708
- replaceShellHooks(hooks, options = {}) {
709
- const replacement = new _HookRegistry();
710
- replacement.loadShellHooks(hooks, options);
711
- const configuredEntries = replacement.entries.slice();
712
- for (let i = this.entries.length - 1; i >= 0; i--) {
713
- const e = this.entries[i];
714
- if (e && e.kind !== "inprocess") this.entries.splice(i, 1);
715
- }
716
- this.entries.push(...configuredEntries);
717
- }
718
- /** All entries registered for an event, in registration order. */
719
- list(event) {
720
- return this.entries.filter((e) => e.event === event);
721
- }
722
- /** True when any entry is registered for the event. */
723
- has(event) {
724
- return this.entries.some((e) => e.event === event);
725
- }
726
- /** Every entry currently registered (across all events). */
727
- all() {
728
- return this.entries.slice();
729
- }
730
- /**
731
- * Drop every in-process hook whose `owner` matches. Used by the plugin
732
- * loader during teardown as a belt-and-braces backstop for the per-call
733
- * unsubscribe functions pushed onto `pluginCleanupFns` — if a plugin
734
- * `setup()` throws partway through after registering some hooks, the
735
- * remaining unsubscribes may never run, and the registry would otherwise
736
- * hold dangling references to a torn-down plugin's closures.
737
- *
738
- * Returns the number of hooks actually removed (useful for tests).
739
- * Shell hooks are owned by the runtime, not a plugin, so they're never
740
- * drained by owner.
741
- */
742
- drainByOwner(owner) {
743
- let removed = 0;
744
- for (let i = this.entries.length - 1; i >= 0; i--) {
745
- const e = this.entries[i];
746
- if (e && e.kind === "inprocess" && e.owner === owner) {
747
- this.entries.splice(i, 1);
748
- removed++;
749
- }
750
- }
751
- return removed;
752
- }
753
- /** Count of in-process hooks currently registered by `owner`. */
754
- countByOwner(owner) {
755
- let n = 0;
756
- for (const e of this.entries) {
757
- if (e.kind === "inprocess" && e.owner === owner) n++;
758
- }
759
- return n;
760
- }
761
- /** Drop every registered hook (used in teardown / tests). */
762
- clear() {
763
- this.entries.length = 0;
764
- }
765
- remove(entry) {
766
- const i = this.entries.indexOf(entry);
767
- if (i >= 0) this.entries.splice(i, 1);
768
- }
769
- };
770
- function hookMatcherMatches(matcher, toolName) {
771
- if (!matcher || matcher === "*") return true;
772
- if (toolName === void 0) return true;
773
- const target = toolName.toLowerCase();
774
- return matcher.split("|").map((s) => s.trim().toLowerCase()).filter(Boolean).includes(target);
775
- }
776
-
777
- // src/hooks/runner.ts
778
- var DEFAULT_TIMEOUT_MS3 = 5e3;
779
- var DEFAULT_BACKGROUND_TIMEOUT_MS = 6e4;
780
- var MAX_TIMEOUT_MS = 10 * 6e4;
781
- function hookTimeoutMs(entry) {
782
- return entry.timeoutMs ?? (entry.background ? DEFAULT_BACKGROUND_TIMEOUT_MS : DEFAULT_TIMEOUT_MS3);
783
- }
784
- function contextFromOutcome(outcome) {
785
- return {
786
- ...outcome.additionalContext ? { additionalContext: outcome.additionalContext } : {},
787
- ...outcome.contextAs ? { contextAs: outcome.contextAs } : {}
788
- };
789
- }
790
- function normalizePreToolOutcome(outcome) {
791
- const context = contextFromOutcome(outcome);
792
- if ("action" in outcome) return { ...outcome, ...context };
793
- if (outcome.decision === "block") {
794
- return {
795
- action: "deny",
796
- reason: outcome.reason?.trim() || "blocked by PreToolUse hook",
797
- ...context
798
- };
799
- }
800
- if (outcome.modifiedInput && typeof outcome.modifiedInput === "object") {
801
- return {
802
- action: "mutate",
803
- input: outcome.modifiedInput,
804
- ...outcome.reason ? { reason: outcome.reason } : {},
805
- ...context
806
- };
807
- }
808
- return { action: "allow", ...context };
809
- }
810
- function malformedOutcome(outcome) {
811
- if (!("action" in outcome)) return void 0;
812
- if (outcome.action === "allow") return void 0;
813
- if (outcome.action === "deny") {
814
- return typeof outcome.reason === "string" && outcome.reason.trim() ? void 0 : "deny outcome requires a non-empty reason";
815
- }
816
- if (outcome.action === "mutate") {
817
- return outcome.input && typeof outcome.input === "object" && !Array.isArray(outcome.input) ? void 0 : "mutate outcome requires an object input";
818
- }
819
- return `unknown hook action: ${String(outcome.action)}`;
820
- }
821
- var HookRunner = class {
822
- constructor(opts) {
823
- this.opts = opts;
824
- this.registry = opts.registry;
825
- }
826
- opts;
827
- registry;
828
- /**
829
- * Background hooks are advisory and must never form an unbounded work queue.
830
- * Keep at most one invocation running plus the latest pending payload for
831
- * each registered hook.
832
- */
833
- backgroundRuns = /* @__PURE__ */ new Map();
834
- /** Cheap guard so callers can skip building payloads when nothing listens. */
835
- has(event) {
836
- return this.registry.has(event);
837
- }
838
- async preToolUse(toolName, toolInput, env, toolMeta) {
839
- const entries = this.matching("PreToolUse", toolName);
840
- if (entries.length === 0) return {};
841
- const declared = {
842
- ...toolMeta?.capabilities ? { toolCapabilities: toolMeta.capabilities } : {},
843
- ...toolMeta?.mutating === void 0 ? {} : { toolMutating: toolMeta.mutating }
844
- };
845
- let current = toolInput;
846
- let mutated = false;
847
- const contexts = [];
848
- let contextAs;
849
- const collectContext = (outcome) => {
850
- if (outcome.additionalContext) contexts.push(outcome.additionalContext);
851
- if (outcome.contextAs === "separate") contextAs = "separate";
852
- else if (outcome.contextAs === "inline" && contextAs === void 0) contextAs = "inline";
853
- };
854
- const resultContext = () => ({
855
- ...contexts.length > 0 ? { additionalContext: contexts.join("\n") } : {},
856
- ...contextAs ? { contextAs } : {}
857
- });
858
- const mutators = entries.filter((entry) => entry.stage === "mutate");
859
- const validators = entries.filter((entry) => entry.stage === "validate");
860
- for (const entry of mutators) {
861
- const payload = {
862
- event: "PreToolUse",
863
- toolName,
864
- toolInput: current,
865
- ...declared,
866
- ...this.base(env)
867
- };
868
- const outcome = await this.invoke(entry, payload, env);
869
- if (!outcome) continue;
870
- const normalized = normalizePreToolOutcome(outcome);
871
- collectContext(normalized);
872
- if (normalized.action === "deny") {
873
- return { block: true, reason: normalized.reason, ...resultContext() };
874
- }
875
- if (normalized.action === "mutate") {
876
- current = normalized.input;
877
- mutated = true;
878
- }
879
- }
880
- for (const entry of validators) {
881
- const payload = {
882
- event: "PreToolUse",
883
- toolName,
884
- toolInput: current,
885
- ...declared,
886
- ...this.base(env)
887
- };
888
- const outcome = await this.invoke(entry, payload, env);
889
- if (!outcome) continue;
890
- const normalized = normalizePreToolOutcome(outcome);
891
- collectContext(normalized);
892
- if (normalized.action === "deny") {
893
- return { block: true, reason: normalized.reason, ...resultContext() };
894
- }
895
- if (normalized.action === "mutate") {
896
- const reason = `PreToolUse validator "${entry.name}" attempted to mutate tool arguments`;
897
- this.opts.logger?.warn?.(reason);
898
- if (entry.failurePolicy === "closed") {
899
- return { block: true, reason, ...resultContext() };
900
- }
901
- }
902
- }
903
- return { ...mutated ? { input: current } : {}, ...resultContext() };
904
- }
905
- async postToolUse(toolName, toolInput, result, env) {
906
- const payload = {
907
- event: "PostToolUse",
908
- toolName,
909
- toolInput,
910
- toolResult: result,
911
- ...this.base(env)
912
- };
913
- const entries = this.matching("PostToolUse", toolName);
914
- if (entries.length === 0) return {};
915
- const foreground = [];
916
- for (const entry of entries) {
917
- if (entry.background) {
918
- this.scheduleBackground(entry, payload, env);
919
- } else {
920
- foreground.push(entry);
921
- }
922
- }
923
- if (foreground.length === 0) return {};
924
- const results = await Promise.allSettled(
925
- foreground.map((entry) => this.invoke(entry, payload, env))
926
- );
927
- const parts = [];
928
- let separate = false;
929
- for (const result2 of results) {
930
- if (result2.status !== "fulfilled" || !result2.value?.additionalContext) continue;
931
- parts.push(result2.value.additionalContext);
932
- if (result2.value.contextAs === "separate") separate = true;
933
- }
934
- return parts.length ? { additionalContext: parts.join("\n"), contextAs: separate ? "separate" : "inline" } : {};
935
- }
936
- async userPromptSubmit(prompt, env) {
937
- const entries = this.matching("UserPromptSubmit", void 0);
938
- if (entries.length === 0) return {};
939
- const payload = { event: "UserPromptSubmit", prompt, ...this.base(env) };
940
- const parts = [];
941
- for (const entry of entries) {
942
- const outcome = await this.invoke(entry, payload, env);
943
- if (!outcome) continue;
944
- const denied = "action" in outcome ? outcome.action === "deny" : outcome.decision === "block";
945
- if (denied) {
946
- const reason = "reason" in outcome ? outcome.reason : void 0;
947
- return { block: true, reason: reason ?? "Blocked by UserPromptSubmit hook" };
948
- }
949
- if (outcome.additionalContext) parts.push(outcome.additionalContext);
950
- }
951
- return parts.length ? { additionalContext: parts.join("\n") } : {};
952
- }
953
- async sessionStart(env) {
954
- const payload = { event: "SessionStart", ...this.base(env) };
955
- return {
956
- additionalContext: await this.collectContext("SessionStart", void 0, payload, env)
957
- };
958
- }
959
- async stop(env) {
960
- const payload = { event: "Stop", ...this.base(env) };
961
- await this.collectContext("Stop", void 0, payload, env);
962
- }
963
- // ── internals ──────────────────────────────────────────────────────
964
- base(env) {
965
- const sessionId = this.opts.sessionId?.();
966
- return sessionId ? { cwd: env.cwd, sessionId } : { cwd: env.cwd };
967
- }
968
- matching(event, toolName) {
969
- return this.registry.list(event).filter((e) => hookMatcherMatches(e.matcher, toolName));
970
- }
971
- scheduleBackground(entry, payload, env) {
972
- const running = this.backgroundRuns.get(entry);
973
- if (running !== void 0) {
974
- running.pending = { payload, env };
975
- return;
976
- }
977
- const state = { pending: null };
978
- this.backgroundRuns.set(entry, state);
979
- void (async () => {
980
- let next = { payload, env };
981
- while (next !== null) {
982
- try {
983
- await this.invoke(entry, next.payload, next.env);
984
- } catch {
985
- }
986
- next = state.pending;
987
- state.pending = null;
988
- }
989
- })().finally(() => {
990
- this.backgroundRuns.delete(entry);
991
- });
992
- }
993
- async invoke(entry, payload, env) {
994
- const allowNonPolicy = this.opts.allowNonPolicy ?? true;
995
- if (!allowNonPolicy && !entry.policy) return null;
996
- let result;
997
- if (entry.kind === "inprocess") {
998
- result = await this.invokeInProcess(entry, payload, env);
999
- } else {
1000
- result = entry.kind === "shell" ? await runShellHookDetailed(
1001
- { command: entry.command, timeoutMs: hookTimeoutMs(entry) },
1002
- payload,
1003
- this.opts.logger,
1004
- { signal: env.signal }
1005
- ) : await runHttpHookDetailed(
1006
- { url: entry.url, headers: entry.headers, timeoutMs: hookTimeoutMs(entry) },
1007
- payload,
1008
- this.opts.logger,
1009
- { signal: env.signal }
1010
- );
1011
- }
1012
- if (!result.failure && result.outcome) {
1013
- const malformed = malformedOutcome(result.outcome);
1014
- if (malformed) {
1015
- result = {
1016
- outcome: null,
1017
- failure: { kind: "invalid_output", message: malformed }
1018
- };
1019
- }
1020
- }
1021
- if (!result.failure) return result.outcome;
1022
- this.opts.logger?.warn?.(
1023
- `${payload.event} ${entry.background ? "background " : ""}hook "${entry.name}" failed (${result.failure.kind}): ${result.failure.message}`
1024
- );
1025
- return this.failureOutcome(entry, payload, result.failure);
1026
- }
1027
- async invokeInProcess(entry, payload, env) {
1028
- const timeoutMs = Math.max(1, Math.min(hookTimeoutMs(entry), MAX_TIMEOUT_MS));
1029
- const controller = new AbortController();
1030
- let timedOut = false;
1031
- const onParentAbort = () => controller.abort(env.signal?.reason);
1032
- if (env.signal?.aborted) {
1033
- return { outcome: null, failure: { kind: "aborted", message: "hook invocation aborted" } };
1034
- }
1035
- env.signal?.addEventListener("abort", onParentAbort, { once: true });
1036
- let timer;
1037
- const timeout = new Promise((_, reject) => {
1038
- timer = setTimeout(() => {
1039
- timedOut = true;
1040
- controller.abort(new Error("hook timeout"));
1041
- reject(new Error(`timed out after ${timeoutMs}ms`));
1042
- }, timeoutMs);
1043
- timer.unref?.();
1044
- });
1045
- const runtime = {
1046
- signal: controller.signal,
1047
- deadlineAt: Date.now() + timeoutMs
1048
- };
1049
- try {
1050
- const invocation = Promise.resolve().then(() => entry.hook(payload, runtime));
1051
- const outcome = await Promise.race([invocation, timeout]);
1052
- if (outcome === void 0) return { outcome: null };
1053
- if (!outcome || typeof outcome !== "object" || Array.isArray(outcome)) {
1054
- return {
1055
- outcome: null,
1056
- failure: {
1057
- kind: "invalid_output",
1058
- message: "in-process hook returned a non-object outcome"
1059
- }
1060
- };
1061
- }
1062
- return { outcome };
1063
- } catch (err) {
1064
- const aborted = env.signal?.aborted === true;
1065
- return {
1066
- outcome: null,
1067
- failure: {
1068
- kind: timedOut ? "timeout" : aborted ? "aborted" : "error",
1069
- message: toErrorMessage(err)
1070
- }
1071
- };
1072
- } finally {
1073
- if (timer) clearTimeout(timer);
1074
- env.signal?.removeEventListener("abort", onParentAbort);
1075
- }
1076
- }
1077
- failureOutcome(entry, payload, failure) {
1078
- if (entry.failurePolicy !== "closed") return null;
1079
- 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.`;
1080
- if (payload.event === "PreToolUse") return { action: "deny", reason };
1081
- if (payload.event === "UserPromptSubmit") return { decision: "block", reason };
1082
- return null;
1083
- }
1084
- async collectContext(event, toolName, payload, env) {
1085
- const entries = this.matching(event, toolName);
1086
- if (entries.length === 0) return void 0;
1087
- const results = await Promise.allSettled(
1088
- entries.map((entry) => this.invoke(entry, payload, env))
1089
- );
1090
- const parts = [];
1091
- for (const result of results) {
1092
- if (result.status === "fulfilled" && result.value?.additionalContext) {
1093
- parts.push(result.value.additionalContext);
1094
- }
1095
- }
1096
- return parts.length ? parts.join("\n") : void 0;
1097
- }
1098
- };
1099
-
1100
- // src/hooks/shell-hooks-equal.ts
1101
- function stableJson(value) {
1102
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
1103
- if (value && typeof value === "object") {
1104
- const entries = Object.entries(value).filter(([, child]) => child !== void 0).sort(([a], [b]) => a.localeCompare(b));
1105
- return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`).join(",")}}`;
1106
- }
1107
- return JSON.stringify(value) ?? "null";
1108
- }
1109
- function shellHooksEqual(a, b) {
1110
- if (a === b) return true;
1111
- if (!a || !b) return !a && !b;
1112
- const aKeys = Object.keys(a);
1113
- const bKeys = Object.keys(b);
1114
- if (aKeys.length !== bKeys.length) return false;
1115
- for (const k of aKeys) {
1116
- const event = k;
1117
- const aList = a[event];
1118
- const bList = b[event];
1119
- if (aList === bList) continue;
1120
- if (!aList || !bList) return false;
1121
- if (aList.length !== bList.length) return false;
1122
- for (let i = 0; i < aList.length; i++) {
1123
- const x = aList[i];
1124
- const y = bList[i];
1125
- if (!x || !y) return !x && !y;
1126
- if (stableJson(x) !== stableJson(y)) return false;
1127
- }
1128
- }
1129
- return true;
1130
- }
1131
- function countShellHooks(hooks) {
1132
- if (!hooks) return 0;
1133
- let n = 0;
1134
- for (const list of Object.values(hooks)) {
1135
- if (list) n += list.length;
1136
- }
1137
- return n;
1138
- }
1
+ import {
2
+ HookRegistry,
3
+ HookRunner,
4
+ countShellHooks,
5
+ hookMatcherMatches,
6
+ parseHookOutcome,
7
+ runHttpHookDetailed,
8
+ runShellHook,
9
+ runShellHookDetailed,
10
+ shellHooksEqual
11
+ } from "../chunk-TWJMGXCI.js";
12
+ import "../chunk-LA7CLDRW.js";
13
+ import "../chunk-6NWEVFEL.js";
14
+ import "../chunk-TBRL2RCC.js";
1139
15
  export {
1140
16
  HookRegistry,
1141
17
  HookRunner,