@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,1510 @@
1
+ import {
2
+ DEFAULT_HQ_REDACTION_POLICY,
3
+ HQ_PROTOCOL_VERSION,
4
+ HQ_TRANSCRIPT_TEXT_CAP,
5
+ createHqEventEnvelope
6
+ } from "./chunk-Z2IJUO45.js";
7
+ import {
8
+ SESSION_RECIPIENT_PREFIX
9
+ } from "./chunk-FWNLEZYG.js";
10
+ import {
11
+ DefaultSecretScrubber
12
+ } from "./chunk-QSTSJOCK.js";
13
+ import {
14
+ SECRET_FILE_MODE,
15
+ restrictFilePermissions
16
+ } from "./chunk-6FHWXLPT.js";
17
+ import {
18
+ readProjectIdentitySync
19
+ } from "./chunk-HI3JGZR2.js";
20
+ import {
21
+ isPidAlive
22
+ } from "./chunk-TRT2HRWZ.js";
23
+ import {
24
+ atomicWrite
25
+ } from "./chunk-OSGLYW5J.js";
26
+ import {
27
+ wstackGlobalRoot
28
+ } from "./chunk-SAFHACFA.js";
29
+
30
+ // src/hq/auth-audit.ts
31
+ import { appendFileSync, readFileSync, mkdirSync } from "node:fs";
32
+ import * as path from "node:path";
33
+ function hqAuthAuditPath(dataDir) {
34
+ return path.join(dataDir, "auth-audit.jsonl");
35
+ }
36
+ function readHqAuthAuditTail(dataDir, maxEntries = 50) {
37
+ const filePath = hqAuthAuditPath(dataDir);
38
+ let content;
39
+ try {
40
+ content = readFileSync(filePath, "utf8");
41
+ } catch {
42
+ return [];
43
+ }
44
+ const lines = content.trimEnd().split("\n");
45
+ const tail = lines.slice(-maxEntries);
46
+ const entries = [];
47
+ for (const line of tail) {
48
+ if (!line) continue;
49
+ try {
50
+ entries.push(JSON.parse(line));
51
+ } catch {
52
+ }
53
+ }
54
+ return entries.reverse();
55
+ }
56
+ function appendHqAuthAudit(dataDir, entry, options) {
57
+ try {
58
+ mkdirSync(dataDir, { recursive: true });
59
+ const line = `${JSON.stringify(entry)}
60
+ `;
61
+ appendFileSync(hqAuthAuditPath(dataDir), line, { encoding: "utf8", flag: "a" });
62
+ } catch (err) {
63
+ options?.onError?.(err instanceof Error ? err : new Error(String(err)));
64
+ }
65
+ }
66
+ function logHqAuthAudit(dataDir, partial, options) {
67
+ const at = partial.at ?? (options?.now?.() ?? Date.now());
68
+ const { at: _omit, ...rest } = partial;
69
+ void _omit;
70
+ appendHqAuthAudit(dataDir, { at, ...rest }, options);
71
+ }
72
+
73
+ // src/hq/auth-store.ts
74
+ import { createHash, randomBytes, randomUUID, scrypt, timingSafeEqual } from "node:crypto";
75
+ import * as syncFs from "node:fs";
76
+ import * as fs from "node:fs/promises";
77
+ import * as path2 from "node:path";
78
+ var HQ_AUTH_FILE_VERSION = 1;
79
+ function defaultHqDataDir() {
80
+ return path2.join(wstackGlobalRoot(), "hq");
81
+ }
82
+ function resolveHqDataDir(override, env = process.env) {
83
+ const raw = override ?? env["WRONGSTACK_HQ_DATA_DIR"]?.trim();
84
+ if (!raw) return defaultHqDataDir();
85
+ return path2.isAbsolute(raw) ? path2.resolve(raw) : path2.resolve(process.cwd(), raw);
86
+ }
87
+ function hqTokenVerifier(secret) {
88
+ return createHash("sha256").update(secret, "utf8").digest("hex");
89
+ }
90
+ function hqTokenKey(token) {
91
+ if (token.verifier !== void 0 && token.verifier.length > 0) return token.verifier;
92
+ return token.token ? hqTokenVerifier(token.token) : "";
93
+ }
94
+ function hashBrowserToken(token) {
95
+ if (token.verifier !== void 0 && token.verifier.length > 0) {
96
+ return token.token ? { ...token, token: "" } : token;
97
+ }
98
+ if (!token.token) return token;
99
+ return { ...token, verifier: hqTokenVerifier(token.token), token: "" };
100
+ }
101
+ function isTokenExpired(token, at = Date.now(), clockSkewMs = 0) {
102
+ if (token === void 0) return false;
103
+ const expiresAt = token.expiresAt;
104
+ if (expiresAt === void 0) return false;
105
+ const parsed = Date.parse(expiresAt);
106
+ if (!Number.isFinite(parsed)) return false;
107
+ return at >= parsed - Math.max(0, clockSkewMs);
108
+ }
109
+ function tokenHasCapability(token, capability, clockSkewMs = 0) {
110
+ if (token === void 0) return false;
111
+ if (isTokenExpired(token, Date.now(), clockSkewMs)) return false;
112
+ if (token.capabilities === void 0) return true;
113
+ return token.capabilities.includes(capability);
114
+ }
115
+ function emptyHqAuthFile() {
116
+ return {
117
+ version: HQ_AUTH_FILE_VERSION,
118
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
119
+ };
120
+ }
121
+ var HQ_AUTH_CONTENT_HASH_REDACTED = "<redacted>";
122
+ function hqAuthContentHash(file) {
123
+ const REDACTED = HQ_AUTH_CONTENT_HASH_REDACTED;
124
+ const redactToken = (t) => ({ ...t, token: REDACTED });
125
+ try {
126
+ const projection = {
127
+ version: file.version,
128
+ updatedAt: file.updatedAt,
129
+ ...file.redactionPolicy !== void 0 ? { redactionPolicy: file.redactionPolicy } : {},
130
+ ...file.browserTokens !== void 0 ? { browserTokens: file.browserTokens.map(redactToken) } : {},
131
+ ...file.clientTokens !== void 0 ? { clientTokens: file.clientTokens.map(redactToken) } : {},
132
+ ...file.passwordHash !== void 0 ? { passwordHash: REDACTED } : {},
133
+ ...file.cookieSecret !== void 0 ? { cookieSecret: REDACTED } : {},
134
+ ...file.totpSecret !== void 0 ? { totpSecret: REDACTED } : {},
135
+ ...file.totpPendingSecret !== void 0 ? { totpPendingSecret: REDACTED } : {},
136
+ ...file.totpRecoveryCodes !== void 0 ? { totpRecoveryCodes: file.totpRecoveryCodes.map(() => REDACTED) } : {},
137
+ ...file.totpLastUsedCounter !== void 0 ? { totpLastUsedCounter: file.totpLastUsedCounter } : {},
138
+ ...file.alertRules !== void 0 ? { alertRules: file.alertRules } : {}
139
+ };
140
+ const payload = JSON.stringify(projection);
141
+ return createHash("sha256").update(payload).digest("hex");
142
+ } catch {
143
+ return void 0;
144
+ }
145
+ }
146
+ function hqAuthFilePath(dataDir) {
147
+ return path2.join(dataDir, "auth.json");
148
+ }
149
+ function hqRuntimeFilePath(dataDir) {
150
+ return path2.join(dataDir, "runtime.json");
151
+ }
152
+ async function writeHqRuntimeFile(dataDir, file) {
153
+ const payload = {
154
+ ...file,
155
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
156
+ };
157
+ const target = hqRuntimeFilePath(dataDir);
158
+ await atomicWrite(target, `${JSON.stringify(payload, null, 2)}
159
+ `, { mode: SECRET_FILE_MODE });
160
+ await restrictFilePermissions(target, { label: "hq-runtime" });
161
+ }
162
+ function readHqRuntimeFileSync(dataDir) {
163
+ try {
164
+ const parsed = JSON.parse(
165
+ syncFs.readFileSync(hqRuntimeFilePath(dataDir), "utf8")
166
+ );
167
+ if (typeof parsed.url !== "string" || parsed.url.trim().length === 0) return void 0;
168
+ if (typeof parsed.pid === "number" && !isPidAlive(parsed.pid)) return void 0;
169
+ return {
170
+ url: parsed.url,
171
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
172
+ ...typeof parsed.pid === "number" ? { pid: parsed.pid } : {}
173
+ };
174
+ } catch {
175
+ return void 0;
176
+ }
177
+ }
178
+ async function readHqAuthFile(dataDir, _opts = {}) {
179
+ const file = hqAuthFilePath(dataDir);
180
+ let raw;
181
+ try {
182
+ raw = await fs.readFile(file, "utf8");
183
+ } catch (err) {
184
+ if (err.code === "ENOENT") return emptyHqAuthFile();
185
+ throw new Error(
186
+ `HQ auth file at ${file} cannot be read: ${err.message}. Fix the file permissions or delete it to start with an empty auth state.`
187
+ );
188
+ }
189
+ let parsed;
190
+ try {
191
+ parsed = JSON.parse(raw);
192
+ } catch (err) {
193
+ throw new Error(
194
+ `HQ auth file at ${file} is not valid JSON: ${err.message}. Fix the file or delete it to start with an empty auth state.`
195
+ );
196
+ }
197
+ if (parsed.version !== HQ_AUTH_FILE_VERSION) {
198
+ throw new Error(
199
+ `HQ auth file at ${file} has unsupported version ${String(parsed.version)} (expected ${String(HQ_AUTH_FILE_VERSION)}). Remove or update the file to match the current schema version.`
200
+ );
201
+ }
202
+ return parsed;
203
+ }
204
+ async function writeHqAuthFile(dataDir, file) {
205
+ const target = hqAuthFilePath(dataDir);
206
+ const payload = {
207
+ ...file,
208
+ version: HQ_AUTH_FILE_VERSION,
209
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
210
+ // WS-044: browser secrets never touch the disk. Any legacy cleartext in
211
+ // the incoming file is converted here, so the first write after an
212
+ // upgrade migrates the whole list — no separate migration step to forget
213
+ // to run, and no window where an old file stays cleartext because nothing
214
+ // happened to rewrite it.
215
+ ...file.browserTokens !== void 0 ? { browserTokens: file.browserTokens.map(hashBrowserToken) } : {}
216
+ };
217
+ await atomicWrite(target, `${JSON.stringify(payload, null, 2)}
218
+ `, { mode: SECRET_FILE_MODE });
219
+ await restrictFilePermissions(target, { label: "hq-auth" });
220
+ }
221
+ async function ensureHqFirstRunAuthFile(dataDir, opts = {}) {
222
+ const file = hqAuthFilePath(dataDir);
223
+ try {
224
+ await fs.access(file);
225
+ const existing = await readHqAuthFile(dataDir, opts);
226
+ if (opts.password) {
227
+ const passwordUnchanged = existing.passwordHash !== void 0 && await verifyHqPassword(opts.password, existing.passwordHash);
228
+ if (!passwordUnchanged) {
229
+ const authFile2 = {
230
+ ...existing,
231
+ passwordHash: await hashHqPassword(opts.password),
232
+ // Rotating the cookie secret invalidates sessions created with the
233
+ // previous password. This matters when --password is used to
234
+ // recover or deliberately change an existing HQ installation.
235
+ cookieSecret: mintHqCookieSecret()
236
+ };
237
+ await writeHqAuthFile(dataDir, authFile2);
238
+ const persisted2 = await readHqAuthFile(dataDir, opts);
239
+ const hash2 = hqAuthContentHash(persisted2);
240
+ const actorField2 = opts.actor ? { actor: opts.actor } : {};
241
+ logHqAuthAudit(
242
+ dataDir,
243
+ {
244
+ kind: "password-rotate",
245
+ scope: "browser",
246
+ tokenId: "(password-rotation)",
247
+ ...hash2 !== void 0 ? { contentHash: hash2 } : {},
248
+ ...actorField2
249
+ },
250
+ {
251
+ onError: (err) => opts.warn?.(`HQ password-rotate audit write failed: ${err.message}`)
252
+ }
253
+ );
254
+ return { authFile: persisted2, created: false };
255
+ }
256
+ }
257
+ return { authFile: existing, created: false };
258
+ } catch (err) {
259
+ if (err.code !== "ENOENT") {
260
+ opts.warn?.(`HQ auth file access failed at ${file}: ${err.message}`);
261
+ return { authFile: await readHqAuthFile(dataDir, opts), created: false };
262
+ }
263
+ }
264
+ const browserToken = {
265
+ ...mintHqToken({ label: "first-run browser", ttlMs: opts.tokenTtlMs }),
266
+ capabilities: ["control.enqueue"]
267
+ };
268
+ const clientToken = {
269
+ ...mintHqToken({ label: "first-run client", ttlMs: opts.tokenTtlMs }),
270
+ capabilities: ["telemetry.publish"]
271
+ };
272
+ const authFile = {
273
+ version: HQ_AUTH_FILE_VERSION,
274
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
275
+ browserTokens: [browserToken],
276
+ clientTokens: [clientToken]
277
+ };
278
+ if (opts.password) {
279
+ authFile.passwordHash = await hashHqPassword(opts.password);
280
+ authFile.cookieSecret = mintHqCookieSecret();
281
+ }
282
+ await writeHqAuthFile(dataDir, authFile);
283
+ const persisted = await readHqAuthFile(dataDir, opts);
284
+ const hash = hqAuthContentHash(persisted);
285
+ const hashField = hash !== void 0 ? { contentHash: hash } : {};
286
+ const actorField = opts.actor ? { actor: opts.actor } : {};
287
+ for (const [scope, token] of [
288
+ ["browser", browserToken],
289
+ ["client", clientToken]
290
+ ]) {
291
+ logHqAuthAudit(
292
+ dataDir,
293
+ {
294
+ kind: "first-run",
295
+ scope,
296
+ tokenId: token.id,
297
+ ...hashField,
298
+ ...token.label ? { label: token.label } : {},
299
+ capabilities: token.capabilities,
300
+ ...token.expiresAt ? { expiresAt: token.expiresAt } : {},
301
+ ...actorField
302
+ },
303
+ {
304
+ onError: (err) => opts.warn?.(`HQ first-run audit write failed: ${err.message}`)
305
+ }
306
+ );
307
+ }
308
+ return { authFile: persisted, created: true, browserToken, clientToken };
309
+ }
310
+ async function mutateHqAuthFile(dataDir, mutator, opts = {}) {
311
+ const current = await readHqAuthFile(dataDir, opts);
312
+ const next = await mutator(current);
313
+ await writeHqAuthFile(dataDir, next);
314
+ return next;
315
+ }
316
+ var HQ_PASSWORD_SALT_BYTES = 16;
317
+ var HQ_PASSWORD_HASH_BYTES = 32;
318
+ var HQ_PASSWORD_SCRYPT_PARAMS = { N: 16384, r: 8, p: 1 };
319
+ function hashHqPassword(password) {
320
+ return new Promise((resolve2, reject) => {
321
+ const salt = randomBytes(HQ_PASSWORD_SALT_BYTES);
322
+ scrypt(password, salt, HQ_PASSWORD_HASH_BYTES, HQ_PASSWORD_SCRYPT_PARAMS, (err, derivedKey) => {
323
+ if (err) return reject(err);
324
+ const payload = `scrypt$${salt.toString("base64url")}$${derivedKey.toString("base64url")}`;
325
+ resolve2(payload);
326
+ });
327
+ });
328
+ }
329
+ async function verifyHqPassword(password, hash) {
330
+ const parts = hash.split("$");
331
+ if (parts.length !== 3 || parts[0] !== "scrypt" || !parts[1] || !parts[2]) return false;
332
+ let salt;
333
+ let expected;
334
+ try {
335
+ salt = Buffer.from(parts[1], "base64url");
336
+ expected = Buffer.from(parts[2], "base64url");
337
+ } catch {
338
+ return false;
339
+ }
340
+ if (salt.length === 0 || expected.length === 0) return false;
341
+ const derived = await new Promise((resolve2, reject) => {
342
+ scrypt(password, salt, expected.length, HQ_PASSWORD_SCRYPT_PARAMS, (err, key) => {
343
+ if (err) reject(err);
344
+ else resolve2(key);
345
+ });
346
+ });
347
+ return timingSafeEqual(derived, expected);
348
+ }
349
+ function mintHqCookieSecret() {
350
+ return randomBytes(32).toString("base64url");
351
+ }
352
+ function mintHqToken(labelOrOptions) {
353
+ const opts = typeof labelOrOptions === "string" ? { label: labelOrOptions } : labelOrOptions ?? {};
354
+ const at = opts.now ?? Date.now();
355
+ const createdAtIso = new Date(at).toISOString();
356
+ return {
357
+ id: randomUUID(),
358
+ token: randomUUID().replace(/-/g, "") + randomUUID().replace(/-/g, ""),
359
+ createdAt: createdAtIso,
360
+ ...opts.label ? { label: opts.label } : {},
361
+ ...opts.ttlMs !== void 0 && Number.isFinite(opts.ttlMs) && opts.ttlMs > 0 ? { expiresAt: new Date(at + opts.ttlMs).toISOString() } : {}
362
+ };
363
+ }
364
+ var mintHqBrowserToken = mintHqToken;
365
+ function watchHqAuthFile(dataDir, onChange, opts = {}) {
366
+ const file = hqAuthFilePath(dataDir);
367
+ const debounceMs = opts.debounceMs ?? 200;
368
+ let timer;
369
+ let closed = false;
370
+ let watcher;
371
+ try {
372
+ watcher = syncFs.watch(path2.dirname(file), { recursive: false });
373
+ } catch (err) {
374
+ opts.warn?.(`HQ auth watcher could not start: ${err.message}`);
375
+ return { close: () => {
376
+ } };
377
+ }
378
+ const trigger = () => {
379
+ if (closed) return;
380
+ if (timer) clearTimeout(timer);
381
+ timer = setTimeout(() => {
382
+ timer = void 0;
383
+ const readOpts = opts.warn ? { warn: opts.warn } : {};
384
+ void readHqAuthFile(dataDir, readOpts).then(
385
+ (next) => {
386
+ if (!closed) onChange(next);
387
+ },
388
+ () => {
389
+ }
390
+ );
391
+ }, debounceMs);
392
+ };
393
+ watcher.on("error", (err) => {
394
+ opts.warn?.(`HQ auth watcher error: ${err.message}`);
395
+ try {
396
+ watcher.close();
397
+ } catch {
398
+ }
399
+ });
400
+ watcher.on("change", (eventType, filename) => {
401
+ const name = typeof filename === "string" ? filename : "";
402
+ if (eventType === "rename" || eventType === "change") {
403
+ if (!name || name === "auth.json" || name === path2.basename(file)) {
404
+ trigger();
405
+ }
406
+ }
407
+ });
408
+ return {
409
+ close: () => {
410
+ closed = true;
411
+ if (timer) clearTimeout(timer);
412
+ watcher.close();
413
+ }
414
+ };
415
+ }
416
+
417
+ // src/hq/redaction.ts
418
+ var RAW_CONTENT_REPLACEMENT = "[REDACTED:hq_raw_content]";
419
+ var SENSITIVE_FIELD_REPLACEMENT = "[REDACTED:hq_sensitive_field]";
420
+ var REDACTED_PATH_REPLACEMENT = "[REDACTED:hq_path]";
421
+ var RAW_CONTENT_KEYS = /* @__PURE__ */ new Set([
422
+ "content",
423
+ "contents",
424
+ "raw",
425
+ "rawContent",
426
+ "body",
427
+ "message",
428
+ "messages",
429
+ "prompt",
430
+ "prompts",
431
+ "completion",
432
+ "response",
433
+ "text",
434
+ "partialText",
435
+ "assistantMessage",
436
+ "userMessage",
437
+ "stdout",
438
+ "stderr",
439
+ "output",
440
+ "toolInput",
441
+ "toolOutput",
442
+ "outputSummary",
443
+ "body",
444
+ "mailboxBody",
445
+ "messageBody",
446
+ "outcome",
447
+ "fileContent",
448
+ "diff",
449
+ "patch",
450
+ "transcript",
451
+ "logs"
452
+ ]);
453
+ var HQ_PROJECT_STATE_EVENT_TYPES = /* @__PURE__ */ new Set(["kanban.snapshot"]);
454
+ var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
455
+ "authorization",
456
+ "cookie",
457
+ "password",
458
+ "passwd",
459
+ "pwd",
460
+ "secret",
461
+ "token",
462
+ "accessToken",
463
+ "refreshToken",
464
+ "apiKey",
465
+ "apikey",
466
+ "clientSecret",
467
+ "privateKey"
468
+ ]);
469
+ var PATH_KEYS = /* @__PURE__ */ new Set([
470
+ "path",
471
+ "paths",
472
+ "file",
473
+ "files",
474
+ "filepath",
475
+ "filePath",
476
+ "filename",
477
+ "projectRoot",
478
+ "projectRootDisplay",
479
+ "cwd",
480
+ "workingDir",
481
+ "workingDirectory"
482
+ ]);
483
+ var defaultScrubber = new DefaultSecretScrubber();
484
+ function resolveHqRedactionPolicy(policy) {
485
+ return {
486
+ ...DEFAULT_HQ_REDACTION_POLICY,
487
+ ...policy
488
+ };
489
+ }
490
+ function tightenHqRedactionPolicy(publisherPolicy, operatorPolicy) {
491
+ if (operatorPolicy === void 0) return publisherPolicy;
492
+ const toolRank = {
493
+ none: 0,
494
+ summary: 1,
495
+ redacted: 2,
496
+ full: 3
497
+ };
498
+ const pathRank = {
499
+ none: 0,
500
+ redacted: 0,
501
+ "project-relative": 1,
502
+ full: 2
503
+ };
504
+ const requestedToolArgs = operatorPolicy.toolArgs;
505
+ const requestedPaths = operatorPolicy.paths;
506
+ return {
507
+ rawContent: publisherPolicy.rawContent && (operatorPolicy.rawContent === void 0 || operatorPolicy.rawContent),
508
+ toolArgs: requestedToolArgs !== void 0 && toolRank[requestedToolArgs] < toolRank[publisherPolicy.toolArgs] ? requestedToolArgs : publisherPolicy.toolArgs,
509
+ paths: requestedPaths !== void 0 && pathRank[requestedPaths] < pathRank[publisherPolicy.paths] ? requestedPaths : publisherPolicy.paths
510
+ };
511
+ }
512
+ function isPlainObject(value) {
513
+ return Object.prototype.toString.call(value) === "[object Object]";
514
+ }
515
+ function isSensitiveKey(key) {
516
+ const lower = key.toLowerCase();
517
+ return SENSITIVE_KEYS.has(key) || lower.includes("authorization") || lower.includes("password") || lower.includes("secret") || lower.endsWith("token") || lower.endsWith("apikey") || lower.endsWith("api_key");
518
+ }
519
+ function isRawContentKey(key) {
520
+ return RAW_CONTENT_KEYS.has(key) || RAW_CONTENT_KEYS.has(key.toLowerCase());
521
+ }
522
+ function isPathKey(key) {
523
+ return PATH_KEYS.has(key) || PATH_KEYS.has(key.toLowerCase());
524
+ }
525
+ function normalizePathForCompare(value) {
526
+ return value.replace(/\\/g, "/").replace(/\/+$/g, "");
527
+ }
528
+ function redactPath(value, projectRoot, policy) {
529
+ if (policy.paths === "full") return value;
530
+ if (policy.paths === "none" || policy.paths === "redacted") return REDACTED_PATH_REPLACEMENT;
531
+ const normalizedValue = normalizePathForCompare(value);
532
+ if (!projectRoot) return normalizedValue.split("/").at(-1) ?? REDACTED_PATH_REPLACEMENT;
533
+ const normalizedRoot = normalizePathForCompare(projectRoot);
534
+ if (normalizedValue === normalizedRoot) return ".";
535
+ if (normalizedValue.startsWith(`${normalizedRoot}/`)) {
536
+ return normalizedValue.slice(normalizedRoot.length + 1) || ".";
537
+ }
538
+ return normalizedValue.split("/").at(-1) ?? REDACTED_PATH_REPLACEMENT;
539
+ }
540
+ function summarizeString(value, maxSummaryLength, scrub) {
541
+ const scrubbed = scrub ? defaultScrubber.scrub(value) : value;
542
+ if (scrubbed.length <= maxSummaryLength) return scrubbed;
543
+ return `${scrubbed.slice(0, maxSummaryLength)}\u2026[truncated:${scrubbed.length - maxSummaryLength}]`;
544
+ }
545
+ function scrubAndTruncateHqPreview(value, maxLength = 280) {
546
+ if (typeof value !== "string" || value.length === 0) return void 0;
547
+ return summarizeString(value, maxLength, true);
548
+ }
549
+ function visitValue(value, options, key) {
550
+ if (key !== void 0 && isSensitiveKey(key)) {
551
+ return { value: SENSITIVE_FIELD_REPLACEMENT, redacted: true };
552
+ }
553
+ if (typeof value === "string") {
554
+ if (key !== void 0 && isPathKey(key)) {
555
+ const redactedPath = redactPath(value, options.projectRoot, options.policy);
556
+ return { value: redactedPath, redacted: redactedPath !== value };
557
+ }
558
+ if (!options.preserveRawContentKeys && !options.policy.rawContent && key !== void 0 && isRawContentKey(key)) {
559
+ return { value: RAW_CONTENT_REPLACEMENT, redacted: true };
560
+ }
561
+ const scrubbed = summarizeString(value, options.maxSummaryLength, true);
562
+ return { value: scrubbed, redacted: scrubbed !== value };
563
+ }
564
+ if (value === null || typeof value !== "object") return { value, redacted: false };
565
+ if (options.seen.has(value)) return { value: "[REDACTED:hq_circular]", redacted: true };
566
+ options.seen.add(value);
567
+ if (Array.isArray(value)) {
568
+ let redacted2 = false;
569
+ const out2 = value.map((item) => {
570
+ const next = visitValue(item, options, key);
571
+ redacted2 ||= next.redacted;
572
+ return next.value;
573
+ });
574
+ return { value: out2, redacted: redacted2 };
575
+ }
576
+ if (!isPlainObject(value)) return { value: String(value), redacted: true };
577
+ let redacted = false;
578
+ const out = {};
579
+ for (const [childKey, childValue] of Object.entries(value)) {
580
+ const next = visitValue(childValue, options, childKey);
581
+ redacted ||= next.redacted;
582
+ out[childKey] = next.value;
583
+ }
584
+ return { value: out, redacted };
585
+ }
586
+ function redactHqValueInternal(value, options, preserveRawContentKeys) {
587
+ const result = visitValue(value, {
588
+ policy: resolveHqRedactionPolicy(options.policy),
589
+ ...options.projectRoot !== void 0 ? { projectRoot: options.projectRoot } : {},
590
+ maxSummaryLength: options.maxSummaryLength ?? 500,
591
+ preserveRawContentKeys,
592
+ seen: /* @__PURE__ */ new WeakSet()
593
+ });
594
+ return { value: result.value, redacted: result.redacted };
595
+ }
596
+ function redactHqValue(value, options = {}) {
597
+ return redactHqValueInternal(value, options, false);
598
+ }
599
+ function redactHqEventPayload(type, payload, options = {}) {
600
+ return redactHqValueInternal(payload, options, HQ_PROJECT_STATE_EVENT_TYPES.has(type));
601
+ }
602
+ function redactHqEvent(event, options = {}) {
603
+ const payload = redactHqEventPayload(event.type, event.payload, options);
604
+ const nextEvent = {
605
+ ...event,
606
+ payload: payload.value
607
+ };
608
+ return { value: nextEvent, redacted: payload.redacted };
609
+ }
610
+ function summarizeHqToolArgs(value, options = {}) {
611
+ const policy = resolveHqRedactionPolicy(options.policy);
612
+ if (policy.toolArgs === "full") return redactHqValue(value, options).value;
613
+ if (policy.toolArgs === "none") return "[REDACTED:hq_tool_args]";
614
+ if (policy.toolArgs === "redacted") return redactHqValue(value, options).value;
615
+ if (value === null || typeof value !== "object") return redactHqValue(value, options).value;
616
+ if (Array.isArray(value)) return `[array:${value.length}]`;
617
+ const entries = Object.entries(value);
618
+ const summary = {};
619
+ for (const [key, item] of entries.slice(0, 20)) {
620
+ if (isSensitiveKey(key)) {
621
+ summary[key] = SENSITIVE_FIELD_REPLACEMENT;
622
+ } else if (typeof item === "string") {
623
+ summary[key] = isPathKey(key) ? redactPath(item, options.projectRoot, policy) : summarizeString(item, 120, true);
624
+ } else if (Array.isArray(item)) {
625
+ summary[key] = `[array:${item.length}]`;
626
+ } else if (item !== null && typeof item === "object") {
627
+ summary[key] = "[object]";
628
+ } else {
629
+ summary[key] = item;
630
+ }
631
+ }
632
+ if (entries.length > 20) summary.__truncatedKeys = entries.length - 20;
633
+ return summary;
634
+ }
635
+
636
+ // src/hq/mailbox-mapper.ts
637
+ function previewText(value, maxLength, policy) {
638
+ if (value === void 0 || value.length === 0) return void 0;
639
+ if (policy?.rawContent === false) return "[REDACTED:hq_raw_content]";
640
+ if (policy?.rawContent === true) {
641
+ if (value.length <= maxLength) return value;
642
+ return `${value.slice(0, maxLength)}\u2026`;
643
+ }
644
+ return scrubAndTruncateHqPreview(value, maxLength);
645
+ }
646
+ function readCount(message) {
647
+ return Object.keys(message.readBy).length;
648
+ }
649
+ function classifyMailboxRecipient(to) {
650
+ if (to === "*" || to === "all") return { scope: "project" };
651
+ if (to.startsWith(SESSION_RECIPIENT_PREFIX)) {
652
+ const recipientSessionId = to.slice(SESSION_RECIPIENT_PREFIX.length);
653
+ if (recipientSessionId.length === 0) return { scope: "agent" };
654
+ return { scope: "session", recipientSessionId };
655
+ }
656
+ return { scope: "agent" };
657
+ }
658
+ function taskSummary(message) {
659
+ if (message.taskContext === void 0) return void 0;
660
+ const task = {
661
+ ...message.taskContext.taskId !== void 0 ? { taskId: message.taskContext.taskId } : {},
662
+ ...message.taskContext.agentRole !== void 0 ? { agentRole: message.taskContext.agentRole } : {},
663
+ ...message.taskContext.agentName !== void 0 ? { agentName: message.taskContext.agentName } : {},
664
+ ...message.taskContext.status !== void 0 ? { status: message.taskContext.status } : {}
665
+ };
666
+ return Object.keys(task).length > 0 ? task : void 0;
667
+ }
668
+ function mapMailboxMessageToHqSummary(message, options = {}) {
669
+ const previewLength = options.previewLength ?? 500;
670
+ const bodyPreview = previewText(message.body, previewLength, options.redactionPolicy);
671
+ const outcomePreview = previewText(message.outcome, previewLength, options.redactionPolicy);
672
+ const task = taskSummary(message);
673
+ const recipient = classifyMailboxRecipient(message.to);
674
+ return {
675
+ mailId: message.id,
676
+ // unique UUID per message record, used for deduplication
677
+ messageId: message.id,
678
+ from: message.from,
679
+ to: message.to,
680
+ scope: recipient.scope,
681
+ ...recipient.recipientSessionId !== void 0 ? { recipientSessionId: recipient.recipientSessionId } : {},
682
+ type: message.type,
683
+ ...message.audience !== void 0 ? { audience: message.audience } : {},
684
+ subject: previewText(message.subject, previewLength, options.redactionPolicy) ?? "",
685
+ priority: message.priority,
686
+ timestamp: message.timestamp,
687
+ ...message.replyTo !== void 0 ? { replyTo: message.replyTo } : {},
688
+ ...message.senderSessionId !== void 0 ? { senderSessionId: message.senderSessionId } : {},
689
+ completed: message.completed,
690
+ ...message.completedBy !== void 0 ? { completedBy: message.completedBy } : {},
691
+ ...message.completedAt !== void 0 ? { completedAt: message.completedAt } : {},
692
+ readCount: readCount(message),
693
+ hasBody: message.body.length > 0,
694
+ ...bodyPreview !== void 0 ? { bodyPreview } : {},
695
+ ...outcomePreview !== void 0 ? { outcomePreview } : {},
696
+ ...task !== void 0 ? { task } : {}
697
+ };
698
+ }
699
+ function mapMailboxAgentToHqSummary(agent) {
700
+ return {
701
+ agentId: agent.agentId,
702
+ name: agent.name,
703
+ ...agent.role !== void 0 ? { role: agent.role } : {},
704
+ sessionId: agent.sessionId,
705
+ status: agent.status,
706
+ ...agent.currentTool !== void 0 ? { currentTool: agent.currentTool } : {},
707
+ ...agent.currentTask !== void 0 ? { currentTask: agent.currentTask } : {},
708
+ iterations: agent.iterations,
709
+ toolCalls: agent.toolCalls,
710
+ lastActivityAt: agent.lastActivityAt,
711
+ lastSeenAt: agent.lastSeenAt,
712
+ online: agent.online,
713
+ ...agent.source !== void 0 ? { source: agent.source } : {}
714
+ };
715
+ }
716
+ function createMailboxSnapshotPayload(messages, agents, options) {
717
+ const includeCompleted = options.includeCompleted ?? true;
718
+ const filteredMessages = includeCompleted ? messages : messages.filter((message) => !message.completed);
719
+ const limit = options.limit ?? 50;
720
+ const sortedMessages = [...filteredMessages].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, limit);
721
+ const summaries = sortedMessages.map((message) => mapMailboxMessageToHqSummary(message, options));
722
+ const agentSummaries = agents.map(mapMailboxAgentToHqSummary);
723
+ return {
724
+ mailboxId: options.mailboxId,
725
+ scope: options.scope ?? "project",
726
+ messages: summaries,
727
+ agents: agentSummaries,
728
+ totals: {
729
+ messages: messages.length,
730
+ unread: messages.filter((message) => !message.completed && readCount(message) === 0).length,
731
+ incomplete: messages.filter((message) => !message.completed).length,
732
+ highPriority: messages.filter((message) => message.priority === "high").length,
733
+ onlineAgents: agents.filter((agent) => agent.online).length
734
+ }
735
+ };
736
+ }
737
+ async function createMailboxSnapshotPayloadFromMailbox(mailbox, options) {
738
+ const limit = options.limit ?? 50;
739
+ const [messages, agents] = await Promise.all([
740
+ mailbox.query({ limit }),
741
+ mailbox.getAgentStatuses()
742
+ ]);
743
+ return createMailboxSnapshotPayload(messages, agents, options);
744
+ }
745
+ function createMailboxEventPayload(input) {
746
+ const summaryOptions = {
747
+ ...input.previewLength !== void 0 ? { previewLength: input.previewLength } : {},
748
+ ...input.redactionPolicy !== void 0 ? { redactionPolicy: input.redactionPolicy } : {}
749
+ };
750
+ return {
751
+ mailboxId: input.mailboxId,
752
+ action: input.action,
753
+ ...input.message !== void 0 ? { message: mapMailboxMessageToHqSummary(input.message, summaryOptions) } : {},
754
+ ...input.agent !== void 0 ? { agent: mapMailboxAgentToHqSummary(input.agent) } : {},
755
+ ...input.summary !== void 0 ? { summary: previewText(input.summary, input.previewLength ?? 160, input.redactionPolicy) ?? "" } : {}
756
+ };
757
+ }
758
+
759
+ // src/hq/publisher.ts
760
+ import { randomUUID as randomUUID2 } from "node:crypto";
761
+ import * as v8 from "node:v8";
762
+ var TRANSCRIPT_EVENT_TYPES = /* @__PURE__ */ new Set(["session.transcript", "agent.message"]);
763
+ var OPEN_STATE = 1;
764
+ var CONNECT_WARN_AFTER_FAILURES = 5;
765
+ var DEFAULT_CONNECT_WARN_COOLDOWN_MS = 5 * 6e4;
766
+ var DEFAULT_RECONNECT_BASE_MS = 1e3;
767
+ var DEFAULT_RECONNECT_MAX_MS = 3e4;
768
+ var DEFAULT_DISCOVERY_POLL_MS = 5e3;
769
+ var DEFAULT_MAX_QUEUED_MESSAGES = 2e3;
770
+ var DEFAULT_MAX_QUEUED_BYTES = Math.min(
771
+ 16 * 1024 * 1024,
772
+ Math.floor(v8.getHeapStatistics().heap_size_limit * 0.1)
773
+ );
774
+ var DEFAULT_COMMAND_POLL_INTERVAL_MS = 500;
775
+ var DEFAULT_COMMAND_POLL_LIMIT = 25;
776
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 25e3;
777
+ var warnedEndpoints = /* @__PURE__ */ new Set();
778
+ function resetHqPublisherWarningStateForTests() {
779
+ warnedEndpoints.clear();
780
+ }
781
+ function queuedFrameCoalesceKey(frame) {
782
+ if (frame.type !== "client.event" || !frame.event.type.endsWith(".snapshot")) {
783
+ return void 0;
784
+ }
785
+ const payload = frame.event.payload;
786
+ const chunk = typeof payload?.chunkIndex === "number" && Number.isFinite(payload.chunkIndex) ? String(payload.chunkIndex) : "";
787
+ return [frame.event.type, frame.event.sessionId ?? "", frame.event.runId ?? "", chunk].join("|");
788
+ }
789
+ function defaultSocketFactory(url) {
790
+ const WebSocketCtor = globalThis.WebSocket;
791
+ if (WebSocketCtor === void 0) {
792
+ throw new Error(
793
+ "No global WebSocket implementation is available; provide HqPublisherOptions.socketFactory."
794
+ );
795
+ }
796
+ return new WebSocketCtor(url);
797
+ }
798
+ function toClientUrl(baseUrl, token) {
799
+ const url = new URL(baseUrl);
800
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
801
+ if (url.pathname === "/" || url.pathname === "") url.pathname = "/ws/client";
802
+ if (token !== void 0 && token.length > 0) url.searchParams.set("token", token);
803
+ return url.toString();
804
+ }
805
+ function addSocketListener(socket, type, listener) {
806
+ if (socket.addEventListener !== void 0) {
807
+ socket.addEventListener(type, listener);
808
+ return;
809
+ }
810
+ socket.on?.(type, listener);
811
+ }
812
+ function removeSocketListener(socket, type, listener) {
813
+ if (socket.removeEventListener !== void 0) {
814
+ socket.removeEventListener(type, listener);
815
+ return;
816
+ }
817
+ socket.off?.(type, listener);
818
+ }
819
+ var HqPublisher = class {
820
+ constructor(options) {
821
+ this.options = options;
822
+ this.socketFactory = options.socketFactory ?? defaultSocketFactory;
823
+ this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
824
+ this.idFactory = options.idFactory ?? randomUUID2;
825
+ this.capabilities = options.capabilities ?? [
826
+ "telemetry.publish",
827
+ "mailbox.summary",
828
+ "fleet.summary",
829
+ "session.summary"
830
+ ];
831
+ this.reconnect = options.reconnect ?? true;
832
+ this.reconnectBaseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
833
+ this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
834
+ this.connectWarnAfterFailures = options.connectWarnAfterFailures ?? CONNECT_WARN_AFTER_FAILURES;
835
+ this.connectWarnCooldownMs = options.connectWarnCooldownMs ?? DEFAULT_CONNECT_WARN_COOLDOWN_MS;
836
+ this.maxQueuedMessages = options.maxQueuedMessages ?? DEFAULT_MAX_QUEUED_MESSAGES;
837
+ this.maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
838
+ this.resolvedRedactionPolicy = resolveHqRedactionPolicy(options.redactionPolicy);
839
+ this.logger = options.logger;
840
+ }
841
+ options;
842
+ socketFactory;
843
+ now;
844
+ idFactory;
845
+ capabilities;
846
+ reconnect;
847
+ reconnectBaseMs;
848
+ reconnectMaxMs;
849
+ connectWarnAfterFailures;
850
+ connectWarnCooldownMs;
851
+ maxQueuedMessages;
852
+ maxQueuedBytes;
853
+ resolvedRedactionPolicy;
854
+ logger;
855
+ socket = null;
856
+ seq = 0;
857
+ queue = [];
858
+ queueBytes = 0;
859
+ /**
860
+ * Cumulative count of frames dropped by the bounded outbound queue when the
861
+ * count/byte cap is exceeded while HQ is unreachable. Lifetime statistic —
862
+ * intentionally NOT reset on close()/reconnect, so it reflects total
863
+ * telemetry loss to overflow for this publisher instance.
864
+ */
865
+ droppedFrames = 0;
866
+ /** Cumulative UTF-8 bytes of frames dropped on overflow (see droppedFrames). */
867
+ droppedBytes = 0;
868
+ /** Obsolete state snapshots replaced in-place while HQ was unreachable. */
869
+ coalescedFrames = 0;
870
+ /** UTF-8 bytes released by snapshot coalescing. */
871
+ coalescedBytes = 0;
872
+ stopped = false;
873
+ reconnectAttempt = 0;
874
+ connectWarningEmitted = false;
875
+ lastAttempt = null;
876
+ reconnectTimer = null;
877
+ commandPollTimer = null;
878
+ heartbeatTimer = null;
879
+ lastCommandId;
880
+ connect() {
881
+ if (this.socket !== null || this.stopped) return;
882
+ if (this.reconnectTimer !== null) return;
883
+ let url = this.options.url;
884
+ let token = this.options.token;
885
+ if (this.options.resolveEndpoint !== void 0) {
886
+ const endpoint = this.options.resolveEndpoint();
887
+ if (endpoint === void 0) {
888
+ this.scheduleDiscoveryPoll();
889
+ return;
890
+ }
891
+ url = endpoint.url;
892
+ token = endpoint.token ?? token;
893
+ }
894
+ this.lastAttempt = { url, hadToken: token !== void 0 };
895
+ let socket;
896
+ try {
897
+ socket = this.socketFactory(toClientUrl(url, token), {
898
+ ...token !== void 0 ? { token } : {}
899
+ });
900
+ } catch {
901
+ this.scheduleReconnect();
902
+ return;
903
+ }
904
+ this.socket = socket;
905
+ const onOpen = () => {
906
+ this.reconnectAttempt = 0;
907
+ this.connectWarningEmitted = false;
908
+ if (this.lastAttempt?.url) {
909
+ warnedEndpoints.delete(this.lastAttempt.url);
910
+ }
911
+ if (this.options.url) {
912
+ warnedEndpoints.delete(this.options.url);
913
+ }
914
+ this.sendHelloNow();
915
+ this.flushQueue();
916
+ this.startHeartbeat();
917
+ if (this.options.onCommand !== void 0) {
918
+ this.startCommandPolling();
919
+ this.pollCommands();
920
+ }
921
+ };
922
+ const onMessage = (event) => {
923
+ this.handleServerMessage(event).catch((error) => {
924
+ const message = `WrongStack HQ publisher: server message handling failed: ${error instanceof Error ? error.message : String(error)}`;
925
+ if (this.logger) {
926
+ this.logger.warn(message, { event: "hq.publisher.message_failed" });
927
+ return;
928
+ }
929
+ process.emitWarning(message, { code: "WRONGSTACK_HQ_MESSAGE_FAILED" });
930
+ });
931
+ };
932
+ const onCloseOrError = () => {
933
+ removeSocketListener(socket, "open", onOpen);
934
+ removeSocketListener(socket, "message", onMessage);
935
+ removeSocketListener(socket, "close", onCloseOrError);
936
+ removeSocketListener(socket, "error", onCloseOrError);
937
+ this.stopCommandPolling();
938
+ this.stopHeartbeat();
939
+ if (this.socket === socket) this.socket = null;
940
+ this.scheduleReconnect();
941
+ };
942
+ addSocketListener(socket, "open", onOpen);
943
+ addSocketListener(socket, "message", onMessage);
944
+ addSocketListener(socket, "close", onCloseOrError);
945
+ addSocketListener(socket, "error", onCloseOrError);
946
+ if (socket.readyState === OPEN_STATE) onOpen();
947
+ }
948
+ close() {
949
+ this.stopped = true;
950
+ if (this.reconnectTimer !== null) {
951
+ clearTimeout(this.reconnectTimer);
952
+ this.reconnectTimer = null;
953
+ }
954
+ this.stopCommandPolling();
955
+ this.stopHeartbeat();
956
+ this.queue = [];
957
+ this.queueBytes = 0;
958
+ const socket = this.socket;
959
+ this.socket = null;
960
+ socket?.close(1e3, "hq publisher closed");
961
+ }
962
+ publishEvent(options) {
963
+ const event = createHqEventEnvelope({
964
+ id: this.idFactory(),
965
+ type: options.type,
966
+ timestamp: options.timestamp ?? this.now(),
967
+ clientId: this.options.client.clientId,
968
+ projectId: this.options.project.projectId,
969
+ seq: ++this.seq,
970
+ payload: options.payload,
971
+ ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {},
972
+ ...options.runId !== void 0 ? { runId: options.runId } : {}
973
+ });
974
+ const maxSummaryLength = options.maxSummaryLength ?? (TRANSCRIPT_EVENT_TYPES.has(options.type) ? HQ_TRANSCRIPT_TEXT_CAP : void 0);
975
+ const redacted = redactHqEvent(event, {
976
+ policy: this.resolvedRedactionPolicy,
977
+ projectRoot: this.options.project.projectRoot,
978
+ ...maxSummaryLength !== void 0 ? { maxSummaryLength } : {}
979
+ }).value;
980
+ this.sendFrame({ type: "client.event", event: redacted });
981
+ return redacted;
982
+ }
983
+ async publishMailboxSnapshot(mailbox, options) {
984
+ const payload = await createMailboxSnapshotPayloadFromMailbox(mailbox, {
985
+ ...options,
986
+ redactionPolicy: this.resolvedRedactionPolicy
987
+ });
988
+ return this.publishEvent({
989
+ type: "mailbox.snapshot",
990
+ payload,
991
+ ...options.sessionId !== void 0 ? { sessionId: options.sessionId } : {},
992
+ ...options.timestamp !== void 0 ? { timestamp: options.timestamp } : {}
993
+ });
994
+ }
995
+ publishMailboxEvent(input) {
996
+ const payload = createMailboxEventPayload({
997
+ mailboxId: input.mailboxId,
998
+ action: input.action,
999
+ ...input.message !== void 0 ? { message: input.message } : {},
1000
+ ...input.agent !== void 0 ? { agent: input.agent } : {},
1001
+ ...input.summary !== void 0 ? { summary: input.summary } : {},
1002
+ ...input.previewLength !== void 0 ? { previewLength: input.previewLength } : {},
1003
+ redactionPolicy: this.resolvedRedactionPolicy
1004
+ });
1005
+ return this.publishEvent({
1006
+ type: "mailbox.event",
1007
+ payload,
1008
+ ...input.sessionId !== void 0 ? { sessionId: input.sessionId } : {},
1009
+ ...input.timestamp !== void 0 ? { timestamp: input.timestamp } : {}
1010
+ });
1011
+ }
1012
+ /** The client identity this publisher announced (clientId, kind, machineId, …). */
1013
+ get identity() {
1014
+ return this.options.client;
1015
+ }
1016
+ /** The project identity this publisher is bound to. */
1017
+ get project() {
1018
+ return this.options.project;
1019
+ }
1020
+ /** Effective publisher-side policy applied before any event leaves this process. */
1021
+ get redactionPolicy() {
1022
+ return this.resolvedRedactionPolicy;
1023
+ }
1024
+ /** Current outbound queue pressure — useful for flow control in telemetry bridges. */
1025
+ getQueueStats() {
1026
+ return {
1027
+ entries: this.queue.length,
1028
+ bytes: this.queueBytes,
1029
+ maxBytes: this.maxQueuedBytes,
1030
+ droppedFrames: this.droppedFrames,
1031
+ droppedBytes: this.droppedBytes,
1032
+ coalescedFrames: this.coalescedFrames,
1033
+ coalescedBytes: this.coalescedBytes
1034
+ };
1035
+ }
1036
+ /** Publish a live session/terminal snapshot (state + agents). */
1037
+ publishSessionSnapshot(payload, opts) {
1038
+ return this.publishEvent({
1039
+ type: "session.snapshot",
1040
+ payload,
1041
+ sessionId: payload.sessionId,
1042
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
1043
+ });
1044
+ }
1045
+ /** Publish an incremental batch of transcript turns for a session. */
1046
+ publishTranscriptAppend(payload, opts) {
1047
+ return this.publishEvent({
1048
+ type: "session.transcript",
1049
+ payload,
1050
+ sessionId: payload.sessionId,
1051
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
1052
+ });
1053
+ }
1054
+ /** Mark a session/terminal as ended. */
1055
+ publishSessionEnded(payload, opts) {
1056
+ return this.publishEvent({
1057
+ type: "session.ended",
1058
+ payload,
1059
+ sessionId: payload.sessionId,
1060
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
1061
+ });
1062
+ }
1063
+ /** Publish a fleet (multi-agent coordinator) snapshot. */
1064
+ publishFleetSnapshot(payload, opts) {
1065
+ return this.publishEvent({
1066
+ type: "fleet.snapshot",
1067
+ payload,
1068
+ runId: payload.runId,
1069
+ ...opts?.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
1070
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
1071
+ });
1072
+ }
1073
+ pollCommands() {
1074
+ this.sendFrame({
1075
+ type: "client.command_poll",
1076
+ clientId: this.options.client.clientId,
1077
+ projectId: this.options.project.projectId,
1078
+ ...this.lastCommandId !== void 0 ? { afterCommandId: this.lastCommandId } : {},
1079
+ limit: this.options.commandPollLimit ?? DEFAULT_COMMAND_POLL_LIMIT
1080
+ });
1081
+ }
1082
+ ackCommand(result) {
1083
+ this.sendFrame({
1084
+ type: "client.command_ack",
1085
+ clientId: this.options.client.clientId,
1086
+ projectId: this.options.project.projectId,
1087
+ commandId: result.commandId,
1088
+ status: result.status,
1089
+ ...result.message !== void 0 ? { message: result.message } : {}
1090
+ });
1091
+ }
1092
+ createHelloFrame() {
1093
+ return {
1094
+ type: "client.hello",
1095
+ payload: {
1096
+ protocolVersion: HQ_PROTOCOL_VERSION,
1097
+ client: this.options.client,
1098
+ project: this.options.project,
1099
+ capabilities: this.capabilities,
1100
+ redactionPolicy: this.resolvedRedactionPolicy
1101
+ }
1102
+ };
1103
+ }
1104
+ sendHelloNow() {
1105
+ const socket = this.socket;
1106
+ if (socket?.readyState !== OPEN_STATE) {
1107
+ this.sendFrame(this.createHelloFrame());
1108
+ return;
1109
+ }
1110
+ socket.send(JSON.stringify(this.createHelloFrame()));
1111
+ }
1112
+ sendFrame(frame) {
1113
+ const serialized = JSON.stringify(frame);
1114
+ const socket = this.socket;
1115
+ if (socket?.readyState === OPEN_STATE && this.queue.length === 0) {
1116
+ socket.send(serialized);
1117
+ return;
1118
+ }
1119
+ this.enqueue(serialized, queuedFrameCoalesceKey(frame));
1120
+ this.connect();
1121
+ }
1122
+ enqueue(serialized, coalesceKey) {
1123
+ const bytes = Buffer.byteLength(serialized, "utf8");
1124
+ if (bytes > this.maxQueuedBytes) {
1125
+ this.droppedFrames += 1;
1126
+ this.droppedBytes += bytes;
1127
+ process.emitWarning(
1128
+ `HQ telemetry frame of ${bytes} bytes exceeds the ${this.maxQueuedBytes}-byte offline queue cap and was dropped.`,
1129
+ { code: "WRONGSTACK_HQ_FRAME_TOO_LARGE" }
1130
+ );
1131
+ return;
1132
+ }
1133
+ if (coalesceKey !== void 0) {
1134
+ let existingIndex = -1;
1135
+ for (let index = this.queue.length - 1; index >= 0; index -= 1) {
1136
+ if (this.queue[index]?.coalesceKey === coalesceKey) {
1137
+ existingIndex = index;
1138
+ break;
1139
+ }
1140
+ }
1141
+ if (existingIndex !== -1) {
1142
+ const [obsolete] = this.queue.splice(existingIndex, 1);
1143
+ if (obsolete !== void 0) {
1144
+ this.queueBytes -= obsolete.bytes;
1145
+ this.coalescedFrames += 1;
1146
+ this.coalescedBytes += obsolete.bytes;
1147
+ }
1148
+ }
1149
+ }
1150
+ while ((this.queue.length >= this.maxQueuedMessages || this.queueBytes + bytes > this.maxQueuedBytes) && this.queue.length > 0) {
1151
+ const dropped = this.queue.shift();
1152
+ if (dropped !== void 0) {
1153
+ this.queueBytes -= dropped.bytes;
1154
+ this.droppedFrames += 1;
1155
+ this.droppedBytes += dropped.bytes;
1156
+ }
1157
+ }
1158
+ this.queue.push({ serialized, bytes, coalesceKey });
1159
+ this.queueBytes += bytes;
1160
+ }
1161
+ /** Batch-dequeue up to 50 frames at a time, yielding to the microtask
1162
+ * queue between batches so we don't starve the event loop on reconnect. */
1163
+ flushQueue() {
1164
+ const socket = this.socket;
1165
+ if (socket?.readyState !== OPEN_STATE || this.queue.length === 0) return;
1166
+ const batch = this.queue.splice(0, 50);
1167
+ for (const frame of batch) {
1168
+ socket.send(frame.serialized);
1169
+ this.queueBytes -= frame.bytes;
1170
+ }
1171
+ this.queueBytes = Math.max(0, this.queueBytes);
1172
+ if (this.queue.length > 0) setImmediate(() => this.flushQueue());
1173
+ }
1174
+ startCommandPolling() {
1175
+ if (this.options.onCommand === void 0 || this.commandPollTimer !== null) return;
1176
+ this.commandPollTimer = setInterval(
1177
+ () => this.pollCommands(),
1178
+ this.options.commandPollIntervalMs ?? DEFAULT_COMMAND_POLL_INTERVAL_MS
1179
+ );
1180
+ this.commandPollTimer.unref?.();
1181
+ }
1182
+ stopCommandPolling() {
1183
+ if (this.commandPollTimer === null) return;
1184
+ clearInterval(this.commandPollTimer);
1185
+ this.commandPollTimer = null;
1186
+ }
1187
+ startHeartbeat() {
1188
+ if (this.heartbeatTimer !== null) return;
1189
+ this.heartbeatTimer = setInterval(
1190
+ () => this.publishHeartbeat(),
1191
+ this.options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS
1192
+ );
1193
+ this.heartbeatTimer.unref?.();
1194
+ }
1195
+ stopHeartbeat() {
1196
+ if (this.heartbeatTimer === null) return;
1197
+ clearInterval(this.heartbeatTimer);
1198
+ this.heartbeatTimer = null;
1199
+ }
1200
+ publishHeartbeat() {
1201
+ const startedAt = Date.parse(this.options.client.startedAt);
1202
+ const now = Date.parse(this.now());
1203
+ this.publishEvent({
1204
+ type: "client.heartbeat",
1205
+ payload: {
1206
+ uptimeMs: Number.isFinite(startedAt) && Number.isFinite(now) ? Math.max(0, now - startedAt) : 0,
1207
+ status: "idle"
1208
+ }
1209
+ });
1210
+ }
1211
+ async handleServerMessage(event) {
1212
+ const message = this.parseServerMessage(event);
1213
+ if (message?.type === "hq.command_batch") {
1214
+ await this.handleCommandBatch(message);
1215
+ return;
1216
+ }
1217
+ if (message?.type === "hq.kanban_snapshot") {
1218
+ await this.options.onKanbanSnapshot?.(message.payload);
1219
+ }
1220
+ }
1221
+ parseServerMessage(event) {
1222
+ const data = this.extractMessageData(event);
1223
+ if (data === null) return null;
1224
+ try {
1225
+ const parsed = JSON.parse(data);
1226
+ if (parsed.type === "hq.command_batch" && Array.isArray(parsed.commands)) {
1227
+ return parsed;
1228
+ }
1229
+ if (parsed.type === "hq.kanban_snapshot") {
1230
+ const payload = parsed.payload;
1231
+ if (payload !== void 0 && typeof payload === "object" && typeof payload.projectId === "string" && payload.projectId === this.options.project.projectId && Array.isArray(payload.boards) && Array.isArray(payload.tombstones)) {
1232
+ return parsed;
1233
+ }
1234
+ }
1235
+ return null;
1236
+ } catch {
1237
+ return null;
1238
+ }
1239
+ }
1240
+ extractMessageData(event) {
1241
+ const value = typeof event === "object" && event !== null && "data" in event ? event.data : event;
1242
+ if (typeof value === "string") return value;
1243
+ if (value instanceof ArrayBuffer) return new TextDecoder().decode(value);
1244
+ if (ArrayBuffer.isView(value)) {
1245
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
1246
+ return new TextDecoder().decode(bytes);
1247
+ }
1248
+ return null;
1249
+ }
1250
+ async handleCommandBatch(message) {
1251
+ const handler = this.options.onCommand;
1252
+ if (handler === void 0) return;
1253
+ for (const command of message.commands) {
1254
+ try {
1255
+ const result = await handler(command);
1256
+ if (result !== void 0) this.ackCommand(result);
1257
+ else if (command.requiresAck)
1258
+ this.ackCommand({ commandId: command.commandId, status: "accepted" });
1259
+ } catch (err) {
1260
+ this.ackCommand({
1261
+ commandId: command.commandId,
1262
+ status: "failed",
1263
+ message: err instanceof Error ? err.message : String(err)
1264
+ });
1265
+ }
1266
+ this.lastCommandId = command.commandId;
1267
+ }
1268
+ }
1269
+ scheduleReconnect() {
1270
+ if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
1271
+ const delay = Math.min(this.reconnectMaxMs, this.reconnectBaseMs * 2 ** this.reconnectAttempt);
1272
+ this.reconnectAttempt += 1;
1273
+ if (!this.connectWarningEmitted && this.reconnectAttempt >= this.connectWarnAfterFailures) {
1274
+ this.connectWarningEmitted = true;
1275
+ this.emitConnectWarning();
1276
+ }
1277
+ this.reconnectTimer = setTimeout(() => {
1278
+ this.reconnectTimer = null;
1279
+ this.connect();
1280
+ }, delay);
1281
+ this.reconnectTimer.unref?.();
1282
+ }
1283
+ emitConnectWarning() {
1284
+ const targetUrl = this.lastAttempt?.url ?? this.options.url;
1285
+ if (this.connectWarnCooldownMs > 0) {
1286
+ if (warnedEndpoints.has(targetUrl)) {
1287
+ return false;
1288
+ }
1289
+ warnedEndpoints.add(targetUrl);
1290
+ }
1291
+ const attempt = this.lastAttempt;
1292
+ const message = `WrongStack HQ publisher: ${this.reconnectAttempt} consecutive connection failures${attempt !== null ? ` to ${attempt.url} (client token ${attempt.hadToken ? "present" : "absent"})` : ""}. Either the HQ server is unreachable or it rejected the token (401). If HQ runs in client-token mode, verify WRONGSTACK_HQ_TOKEN / auth.json. Retries continue with backoff.`;
1293
+ if (this.logger) {
1294
+ this.logger.warn(message, { event: "hq.publisher.connect_failed" });
1295
+ return true;
1296
+ }
1297
+ const warn = this.options.warn ?? ((msg) => console.warn(
1298
+ JSON.stringify({
1299
+ level: "warn",
1300
+ event: "hq.publisher.connect_failed",
1301
+ message: msg,
1302
+ timestamp: this.now()
1303
+ })
1304
+ ));
1305
+ try {
1306
+ warn(message);
1307
+ } catch {
1308
+ }
1309
+ return true;
1310
+ }
1311
+ /**
1312
+ * Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED
1313
+ * interval (not the exponential reconnect backoff): the check is a cheap
1314
+ * local file read, and backing off would delay attaching to an HQ the
1315
+ * user just started — the whole point of auto-discovery.
1316
+ */
1317
+ scheduleDiscoveryPoll() {
1318
+ if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
1319
+ this.reconnectAttempt = 0;
1320
+ this.reconnectTimer = setTimeout(() => {
1321
+ this.reconnectTimer = null;
1322
+ this.connect();
1323
+ }, this.options.discoveryPollMs ?? DEFAULT_DISCOVERY_POLL_MS);
1324
+ this.reconnectTimer.unref?.();
1325
+ }
1326
+ };
1327
+
1328
+ // src/hq/factory.ts
1329
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
1330
+ import * as fs2 from "node:fs";
1331
+ import { hostname } from "node:os";
1332
+ import { basename as basename2 } from "node:path";
1333
+ function discoverLocalHqEndpoint(options = {}) {
1334
+ const dataDir = resolveHqDataDir(options.dataDir, options.env ?? process.env);
1335
+ const runtime = readHqRuntimeFileSync(dataDir);
1336
+ if (runtime === void 0) return void 0;
1337
+ if (!isLoopbackHqUrl(runtime.url)) return void 0;
1338
+ const token = readFirstClientTokenFromAuthFile(dataDir);
1339
+ return { url: runtime.url, ...token ? { token } : {} };
1340
+ }
1341
+ function readFirstClientTokenFromAuthFile(dataDir) {
1342
+ try {
1343
+ const raw = fs2.readFileSync(hqAuthFilePath(dataDir), "utf8");
1344
+ const parsed = JSON.parse(raw);
1345
+ return parsed.clientTokens?.find((t) => t.token.trim().length > 0)?.token;
1346
+ } catch {
1347
+ return void 0;
1348
+ }
1349
+ }
1350
+ function isLoopbackHqUrl(url) {
1351
+ try {
1352
+ const host = new URL(url).hostname.toLowerCase();
1353
+ return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]" || host === "0.0.0.0";
1354
+ } catch {
1355
+ return false;
1356
+ }
1357
+ }
1358
+ function resolveHqConfigFromEnv(env = process.env) {
1359
+ return resolveHqConfig({ env });
1360
+ }
1361
+ function resolveHqConfig(options = {}) {
1362
+ const env = options.env ?? process.env;
1363
+ const fileConfig = options.config;
1364
+ const envUrl = env["WRONGSTACK_HQ_URL"]?.trim();
1365
+ const envToken = env["WRONGSTACK_HQ_TOKEN"]?.trim();
1366
+ const configUrl = fileConfig?.url?.trim();
1367
+ const configToken = fileConfig?.token?.trim();
1368
+ const envEnabledRaw = env["WRONGSTACK_HQ_ENABLED"]?.trim();
1369
+ const enabled = envEnabledRaw !== void 0 && envEnabledRaw.length > 0 ? envEnabledRaw !== "0" : fileConfig?.enabled;
1370
+ const dataDir = resolveHqDataDir(fileConfig?.dataDir, env);
1371
+ const explicitToken = envToken || configToken;
1372
+ const runtimeUrl = readHqRuntimeFileSync(dataDir)?.url.trim();
1373
+ const url = envUrl || configUrl;
1374
+ const localFallbackToken = !url || isLoopbackHqUrl(url) ? readFirstClientTokenFromAuthFile(dataDir) : void 0;
1375
+ const token = explicitToken || localFallbackToken;
1376
+ const rawContentEnv = env["WRONGSTACK_HQ_RAW_CONTENT"]?.trim();
1377
+ const explicitRawContent = rawContentEnv ? rawContentEnv === "1" : fileConfig?.rawContent;
1378
+ const projectAliasEnv = env["WRONGSTACK_HQ_PROJECT_ALIAS"]?.trim();
1379
+ const projectAlias = projectAliasEnv || fileConfig?.projectAlias;
1380
+ if (!url) {
1381
+ if (enabled === false) return void 0;
1382
+ return {
1383
+ url: runtimeUrl || "http://127.0.0.1:3499",
1384
+ enabled: true,
1385
+ discover: true,
1386
+ dataDir,
1387
+ rawContent: explicitRawContent ?? true,
1388
+ ...token ? { token } : {},
1389
+ ...projectAlias ? { projectAlias } : {}
1390
+ };
1391
+ }
1392
+ const rawContent = explicitRawContent ?? true;
1393
+ return {
1394
+ url,
1395
+ ...token ? { token } : {},
1396
+ ...enabled !== void 0 ? { enabled } : {},
1397
+ ...rawContent !== void 0 ? { rawContent } : {},
1398
+ ...projectAlias ? { projectAlias } : {}
1399
+ };
1400
+ }
1401
+ function stableMachineId() {
1402
+ return createHash2("sha256").update(hostname()).digest("hex").slice(0, 12);
1403
+ }
1404
+ function deriveHqProjectId(projectRoot, projectAlias) {
1405
+ const projectIdentity = readProjectIdentitySync(projectRoot);
1406
+ if (projectIdentity) return projectIdentity.projectId;
1407
+ const alias = projectAlias?.trim();
1408
+ if (!alias && projectRoot.startsWith("alias:")) {
1409
+ throw new Error('projectRoot must not use the reserved "alias:" HQ identity prefix');
1410
+ }
1411
+ const identity = alias ? `alias:${alias}` : projectRoot;
1412
+ return createHash2("sha256").update(identity).digest("hex").slice(0, 12);
1413
+ }
1414
+ function createHqPublisherFromEnv(options) {
1415
+ const config = options.config ?? resolveHqConfig({ config: options.appConfig?.hq });
1416
+ if (!config || config.enabled === false) return void 0;
1417
+ const machineId = options.machineId ?? stableMachineId();
1418
+ const host = options.hostnameOverride ?? hostname();
1419
+ const projectAlias = config.projectAlias?.trim() || void 0;
1420
+ const projectName = projectAlias ?? options.projectName ?? (basename2(options.projectRoot) || "unknown");
1421
+ const client = {
1422
+ clientId: `${machineId}:${options.clientKind}:${process.pid}:${randomUUID3().slice(0, 8)}`,
1423
+ kind: options.clientKind,
1424
+ machineId,
1425
+ ...host ? { hostname: host } : {},
1426
+ pid: process.pid,
1427
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1428
+ };
1429
+ const project = {
1430
+ projectId: deriveHqProjectId(options.projectRoot, projectAlias),
1431
+ projectRoot: options.projectRoot,
1432
+ projectName,
1433
+ machineId,
1434
+ workspaceKind: "git"
1435
+ };
1436
+ const redactionPolicy = options.redactionPolicy || config.rawContent !== void 0 ? {
1437
+ ...config.rawContent !== void 0 ? { rawContent: config.rawContent } : {},
1438
+ ...options.redactionPolicy ?? {}
1439
+ } : void 0;
1440
+ const discoveryDataDir = config.dataDir;
1441
+ return new HqPublisher({
1442
+ url: config.url,
1443
+ ...config.token ? { token: config.token } : {},
1444
+ client,
1445
+ project,
1446
+ ...options.socketFactory ? { socketFactory: options.socketFactory } : {},
1447
+ ...redactionPolicy !== void 0 ? { redactionPolicy } : {},
1448
+ ...options.capabilities !== void 0 ? { capabilities: options.capabilities } : {},
1449
+ ...options.maxQueuedMessages !== void 0 ? { maxQueuedMessages: options.maxQueuedMessages } : {},
1450
+ ...options.onCommand !== void 0 ? { onCommand: options.onCommand } : {},
1451
+ ...options.onKanbanSnapshot !== void 0 ? { onKanbanSnapshot: options.onKanbanSnapshot } : {},
1452
+ // Auto-discovery: re-read the local HQ runtime marker + client token on
1453
+ // every connect attempt so late-started/restarted HQs are picked up.
1454
+ ...config.discover ? { resolveEndpoint: () => discoverLocalHqEndpoint({ dataDir: discoveryDataDir }) } : {},
1455
+ ...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {},
1456
+ ...options.logger !== void 0 ? { logger: options.logger } : {}
1457
+ });
1458
+ }
1459
+
1460
+ export {
1461
+ hqAuthAuditPath,
1462
+ readHqAuthAuditTail,
1463
+ appendHqAuthAudit,
1464
+ logHqAuthAudit,
1465
+ HQ_AUTH_FILE_VERSION,
1466
+ defaultHqDataDir,
1467
+ resolveHqDataDir,
1468
+ hqTokenVerifier,
1469
+ hqTokenKey,
1470
+ isTokenExpired,
1471
+ tokenHasCapability,
1472
+ emptyHqAuthFile,
1473
+ HQ_AUTH_CONTENT_HASH_REDACTED,
1474
+ hqAuthContentHash,
1475
+ hqAuthFilePath,
1476
+ hqRuntimeFilePath,
1477
+ writeHqRuntimeFile,
1478
+ readHqRuntimeFileSync,
1479
+ readHqAuthFile,
1480
+ writeHqAuthFile,
1481
+ ensureHqFirstRunAuthFile,
1482
+ mutateHqAuthFile,
1483
+ hashHqPassword,
1484
+ verifyHqPassword,
1485
+ mintHqCookieSecret,
1486
+ mintHqToken,
1487
+ mintHqBrowserToken,
1488
+ watchHqAuthFile,
1489
+ resolveHqRedactionPolicy,
1490
+ tightenHqRedactionPolicy,
1491
+ scrubAndTruncateHqPreview,
1492
+ redactHqValue,
1493
+ redactHqEventPayload,
1494
+ redactHqEvent,
1495
+ summarizeHqToolArgs,
1496
+ classifyMailboxRecipient,
1497
+ mapMailboxMessageToHqSummary,
1498
+ mapMailboxAgentToHqSummary,
1499
+ createMailboxSnapshotPayload,
1500
+ createMailboxSnapshotPayloadFromMailbox,
1501
+ createMailboxEventPayload,
1502
+ resetHqPublisherWarningStateForTests,
1503
+ HqPublisher,
1504
+ discoverLocalHqEndpoint,
1505
+ resolveHqConfigFromEnv,
1506
+ resolveHqConfig,
1507
+ deriveHqProjectId,
1508
+ createHqPublisherFromEnv
1509
+ };
1510
+ //# sourceMappingURL=chunk-NIOAGFPB.js.map