@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,3276 +1,48 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/coordination/mailbox-project-server.ts
4
- import { randomBytes as randomBytes2 } from "node:crypto";
5
- import * as fsPromises from "node:fs/promises";
6
- import * as net from "node:net";
7
- import * as path7 from "node:path";
8
- import { bindProjectEndpoint } from "@wrongstack/persistence";
9
-
10
- // src/kernel/events.ts
11
- var MAX_WILDCARDS = 500;
12
- var MAX_NAMED_LISTENERS = 2e3;
13
- var EventBus = class {
14
- listeners = /* @__PURE__ */ new Map();
15
- wildcards = [];
16
- logger;
17
- /**
18
- * Dispatch arrays cached per event name, rebuilt lazily after a
19
- * subscription change. See {@link namedSnapshot}. Every mutation of
20
- * `listeners` must invalidate the matching entry, and every mutation of
21
- * `wildcards` must null `wildcardSnapshotCache` — a missed invalidation
22
- * means an emit dispatches to a stale listener set.
23
- */
24
- listenerSnapshots = /* @__PURE__ */ new Map();
25
- wildcardSnapshotCache = null;
26
- setLogger(logger) {
27
- this.logger = logger;
28
- }
29
- on(event, fn) {
30
- if (this.listenerCount() >= MAX_NAMED_LISTENERS) {
31
- this.logger?.error(
32
- `EventBus named listener limit (~${MAX_NAMED_LISTENERS}) reached \u2014 rejecting on("${event}"). Callers must dispose their named listeners to prevent unbounded memory growth.`
33
- );
34
- return () => {
35
- };
36
- }
37
- let set = this.listeners.get(event);
38
- if (!set) {
39
- set = /* @__PURE__ */ new Set();
40
- this.listeners.set(event, set);
41
- }
42
- set.add(fn);
43
- this.listenerSnapshots.delete(event);
44
- return () => this.off(event, fn);
45
- }
46
- off(event, fn) {
47
- const set = this.listeners.get(event);
48
- if (!set) return;
49
- set.delete(fn);
50
- this.listenerSnapshots.delete(event);
51
- if (set.size === 0) this.listeners.delete(event);
52
- }
53
- once(event, fn) {
54
- const wrapper = (payload) => {
55
- this.off(event, wrapper);
56
- fn(payload);
57
- };
58
- this.on(event, wrapper);
59
- return () => {
60
- this.off(event, wrapper);
61
- };
62
- }
63
- /**
64
- * Subscribe to all events, regardless of name. Short-hand for
65
- * `onPattern('*')`. Use for logging, debugging, or forwarding every
66
- * event to another bus (as FleetBus does).
67
- *
68
- * Returns an unsubscribe function.
69
- */
70
- onAny(fn, owner) {
71
- return this.onPattern("*", fn, owner);
72
- }
73
- /**
74
- * Subscribe to all events whose name matches a glob-style prefix.
75
- * `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
76
- * `'*'` matches every event.
77
- *
78
- * The handler receives `(eventName, payload)` with the event name as a
79
- * string and the payload as `unknown`. Use for logging, debugging, or
80
- * metrics collection across a family of events.
81
- *
82
- * Returns an unsubscribe function.
83
- */
84
- onPattern(pattern, fn, owner) {
85
- if (this.wildcards.length >= MAX_WILDCARDS) {
86
- this.logger?.error(
87
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
88
- );
89
- return () => {
90
- };
91
- }
92
- const match = makePatternMatcher(pattern);
93
- const entry = { match, fn, owner };
94
- this.wildcards.push(entry);
95
- this.wildcardSnapshotCache = null;
96
- return () => {
97
- const idx = this.wildcards.indexOf(entry);
98
- if (idx >= 0) {
99
- this.wildcards.splice(idx, 1);
100
- this.wildcardSnapshotCache = null;
101
- }
102
- };
103
- }
104
- /**
105
- * Subscribe to all events whose name matches a RegExp.
106
- * More flexible than `onPattern` — use when you need regex features
107
- * (alternation, character classes, capture groups).
108
- *
109
- * Returns an unsubscribe function.
110
- */
111
- onRegex(regex, fn, owner) {
112
- if (this.wildcards.length >= MAX_WILDCARDS) {
113
- this.logger?.error(
114
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
115
- );
116
- return () => {
117
- };
118
- }
119
- const entry = { match: (e) => regex.test(e), fn, owner };
120
- this.wildcards.push(entry);
121
- this.wildcardSnapshotCache = null;
122
- return () => {
123
- const idx = this.wildcards.indexOf(entry);
124
- if (idx >= 0) {
125
- this.wildcards.splice(idx, 1);
126
- this.wildcardSnapshotCache = null;
127
- }
128
- };
129
- }
130
- emit(event, payload) {
131
- const snapshot = this.namedSnapshot(event);
132
- if (snapshot !== void 0) {
133
- for (const fn of snapshot) {
134
- try {
135
- fn(payload);
136
- } catch (err) {
137
- this.logger?.error(`EventBus listener for "${event}" threw`, err);
138
- }
139
- }
140
- }
141
- if (this.wildcards.length > 0) {
142
- const name = event;
143
- for (const { match, fn } of this.wildcardSnapshot()) {
144
- if (!match(name)) continue;
145
- try {
146
- fn(name, payload);
147
- } catch (err) {
148
- this.logger?.error(`EventBus wildcard listener for "${name}" threw`, err);
149
- }
150
- }
151
- }
152
- }
153
- /**
154
- * Dispatch array for one event name, or `undefined` when nothing is
155
- * subscribed.
156
- *
157
- * Dispatch iterates a stable array rather than the live Set so a listener
158
- * that subscribes or unsubscribes mid-emit cannot change what this round
159
- * delivers: an addition fires from the next emit, a removal may still fire
160
- * this round. That is the contract callers rely on, and it is unchanged.
161
- *
162
- * What changed is who pays for it. Building the array per emit did O(number
163
- * of listeners) copying on every event, including `tool.progress` and
164
- * streaming deltas — the highest-frequency paths in the process. The array
165
- * is now cached and rebuilt only when the subscription set actually changes,
166
- * which is wiring time and essentially never during a run. Measured at 2M
167
- * emits with 12 named + 6 wildcard listeners: 207 ms → 141 ms.
168
- *
169
- * This is a throughput win, not a footprint one: the per-emit arrays died in
170
- * the nursery and never showed up as retained heap (measured heap growth was
171
- * the same either way). Do not cite this as a memory fix.
172
- *
173
- * A mutation during dispatch invalidates the cache for the *next* emit while
174
- * the in-flight loop keeps walking the array it started with — which is
175
- * exactly the snapshot semantics described above.
176
- */
177
- namedSnapshot(event) {
178
- const cached = this.listenerSnapshots.get(event);
179
- if (cached !== void 0) return cached;
180
- const set = this.listeners.get(event);
181
- if (!set || set.size === 0) return void 0;
182
- const snapshot = [...set];
183
- this.listenerSnapshots.set(event, snapshot);
184
- return snapshot;
185
- }
186
- /** Wildcard counterpart to {@link namedSnapshot}; same caching rationale. */
187
- wildcardSnapshot() {
188
- this.wildcardSnapshotCache ??= this.wildcards.slice();
189
- return this.wildcardSnapshotCache;
190
- }
191
- /**
192
- * Emit a plugin-defined event that is intentionally outside EventMap.
193
- * Custom events are delivered to wildcard/pattern listeners only; typed
194
- * listeners remain reserved for core EventMap keys.
195
- */
196
- emitCustom(event, payload) {
197
- if (this.wildcards.length === 0) return;
198
- for (const { match, fn } of this.wildcardSnapshot()) {
199
- if (!match(event)) continue;
200
- try {
201
- fn(event, payload);
202
- } catch (err) {
203
- this.logger?.error(`EventBus wildcard listener for "${event}" threw`, err);
204
- }
205
- }
206
- }
207
- clear() {
208
- this.listeners.clear();
209
- this.wildcards.length = 0;
210
- this.listenerSnapshots.clear();
211
- this.wildcardSnapshotCache = null;
212
- }
213
- /**
214
- * V2-D: introspection helper. Pass an `event` to count handlers for a
215
- * single key, or omit to get the total across every event. Used by the
216
- * leak-detection smoke test to flag handler accumulation across runs.
217
- * Does NOT include wildcard listeners.
218
- */
219
- listenerCount(event) {
220
- if (event !== void 0) return this.listeners.get(event)?.size ?? 0;
221
- let total = 0;
222
- for (const set of this.listeners.values()) total += set.size;
223
- return total;
224
- }
225
- /**
226
- * Number of wildcard listeners currently registered.
227
- */
228
- wildcardCount() {
229
- return this.wildcards.length;
230
- }
231
- /**
232
- * True if anything would receive an emit for `event` — a named listener
233
- * OR a wildcard/regex pattern that matches the event name. Unlike
234
- * `listenerCount`, this DOES account for wildcards, so callers that gate
235
- * behavior on "is anyone listening?" (e.g. SubagentBudget deciding whether
236
- * to negotiate a soft limit vs hard-stop) don't misfire when the only
237
- * subscriber is a pattern listener like the FleetBus's `onPattern('*')`.
238
- */
239
- hasListenerFor(event) {
240
- if ((this.listeners.get(event)?.size ?? 0) > 0) return true;
241
- return this.wildcards.some((w) => w.match(event));
242
- }
243
- };
244
- var MATCH_ALL = () => true;
245
- function makePatternMatcher(pattern) {
246
- if (pattern === "*") return MATCH_ALL;
247
- if (pattern.endsWith(".*")) {
248
- const prefix = pattern.slice(0, -2);
249
- return (e) => e.startsWith(`${prefix}.`);
250
- }
251
- return (e) => e === pattern;
252
- }
253
-
254
- // src/security/file-permissions.ts
255
2
  import {
256
- restrictDirPermissions,
257
- restrictFilePermissions,
258
- SECRET_DIR_MODE,
259
- SECRET_FILE_MODE
260
- } from "@wrongstack/persistence";
261
-
262
- // src/utils/atomic-write.ts
3
+ MailboxEventEmitter
4
+ } from "../chunk-JX7VBZDK.js";
263
5
  import {
264
- createPersistencePrimitives
265
- } from "@wrongstack/persistence";
266
-
267
- // src/types/errors.ts
268
- var ERROR_CODES = {
269
- // Provider
270
- PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
271
- PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
272
- PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
273
- PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
274
- PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
275
- PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR",
276
- PROVIDER_CONTEXT_OVERFLOW: "PROVIDER_CONTEXT_OVERFLOW",
277
- // Tool
278
- TOOL_NOT_FOUND: "TOOL_NOT_FOUND",
279
- TOOL_PERMISSION_DENIED: "TOOL_PERMISSION_DENIED",
280
- TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED",
281
- TOOL_TIMEOUT: "TOOL_TIMEOUT",
282
- TOOL_INPUT_INVALID: "TOOL_INPUT_INVALID",
283
- // Config
284
- CONFIG_INVALID: "CONFIG_INVALID",
285
- CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
286
- CONFIG_PARSE_FAILED: "CONFIG_PARSE_FAILED",
287
- CONFIG_MIGRATION_NEEDED: "CONFIG_MIGRATION_NEEDED",
288
- // Plugin
289
- PLUGIN_LOAD_FAILED: "PLUGIN_LOAD_FAILED",
290
- PLUGIN_API_MISMATCH: "PLUGIN_API_MISMATCH",
291
- PLUGIN_MISSING_DEPENDENCY: "PLUGIN_MISSING_DEPENDENCY",
292
- // Agent
293
- AGENT_ITERATION_LIMIT: "AGENT_ITERATION_LIMIT",
294
- AGENT_CONTEXT_OVERFLOW: "AGENT_CONTEXT_OVERFLOW",
295
- AGENT_ABORTED: "AGENT_ABORTED",
296
- AGENT_RUN_FAILED: "AGENT_RUN_FAILED",
297
- // Session
298
- SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
299
- SESSION_CORRUPTED: "SESSION_CORRUPTED",
300
- SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
301
- // Container / Registry
302
- CONTAINER_TOKEN_ALREADY_BOUND: "CONTAINER_TOKEN_ALREADY_BOUND",
303
- CONTAINER_TOKEN_NOT_BOUND: "CONTAINER_TOKEN_NOT_BOUND",
304
- CONTAINER_CIRCULAR_DEPENDENCY: "CONTAINER_CIRCULAR_DEPENDENCY",
305
- REGISTRY_DUPLICATE: "REGISTRY_DUPLICATE",
306
- REGISTRY_NOT_FOUND: "REGISTRY_NOT_FOUND",
307
- REGISTRY_INVALID: "REGISTRY_INVALID",
308
- // File system
309
- FS_READ_FAILED: "FS_READ_FAILED",
310
- FS_WRITE_FAILED: "FS_WRITE_FAILED",
311
- FS_MKDIR_FAILED: "FS_MKDIR_FAILED",
312
- FS_DELETE_FAILED: "FS_DELETE_FAILED",
313
- FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
314
- // SDD (Spec-Driven Development)
315
- SDD_VALIDATION_FAILED: "SDD_VALIDATION_FAILED",
316
- SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
317
- SDD_INVALID_STATE: "SDD_INVALID_STATE",
318
- SDD_NOT_READY: "SDD_NOT_READY",
319
- // General
320
- VALIDATION_ERROR: "VALIDATION_ERROR",
321
- PARSE_FAILED: "PARSE_FAILED",
322
- UNKNOWN: "UNKNOWN"
323
- };
324
- var WrongStackError = class extends Error {
325
- code;
326
- subsystem;
327
- severity;
328
- recoverable;
329
- context;
330
- constructor(opts) {
331
- super(opts.message, { cause: opts.cause });
332
- this.name = "WrongStackError";
333
- this.code = opts.code;
334
- this.subsystem = opts.subsystem;
335
- this.severity = opts.severity ?? "error";
336
- this.recoverable = opts.recoverable ?? false;
337
- this.context = opts.context;
338
- }
339
- /**
340
- * Render a one-line user-facing description.
341
- * Subclasses should override for domain-specific formatting.
342
- */
343
- describe() {
344
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
345
- return `${this.code}: ${this.message}${ctx}`;
346
- }
347
- };
348
- function formatContext(ctx) {
349
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
350
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
351
- }
352
- var FsError = class extends WrongStackError {
353
- path;
354
- constructor(opts) {
355
- super({
356
- message: opts.message,
357
- code: opts.code,
358
- subsystem: "fs",
359
- severity: "error",
360
- recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
361
- context: { path: opts.path, ...opts.context },
362
- cause: opts.cause
363
- });
364
- this.name = "FsError";
365
- this.path = opts.path;
366
- }
367
- };
368
-
369
- // src/utils/atomic-write.ts
370
- var primitives = createPersistencePrimitives({
371
- createLockTimeoutError: ({ targetPath, timeoutMs }) => new FsError({
372
- message: `Timed out waiting for file lock: ${targetPath}`,
373
- code: "FS_ATOMIC_WRITE_FAILED",
374
- path: targetPath,
375
- context: { timeoutMs }
376
- })
377
- });
378
- var atomicWrite = primitives.atomicWrite;
379
- var atomicReplaceWithWriter = primitives.atomicReplaceWithWriter;
380
- var ensureDir = primitives.ensureDir;
381
- var withFileLock = primitives.withFileLock;
382
-
383
- // src/utils/heap-watchdog.ts
384
- import * as fs from "node:fs";
385
- import * as fsp2 from "node:fs/promises";
386
- import * as path3 from "node:path";
6
+ useDaemonPerfDefaults
7
+ } from "../chunk-FNXVT7HF.js";
387
8
  import {
388
- PerformanceObserver,
389
- constants as performanceConstants
390
- } from "node:perf_hooks";
391
- import * as v8 from "node:v8";
392
-
393
- // src/utils/memory-flight-recorder.ts
394
- import * as fsp from "node:fs/promises";
395
- import * as path2 from "node:path";
396
-
397
- // src/utils/continuous-memory-profiler.ts
398
- import { Session } from "node:inspector/promises";
399
- var DEFAULT_SAMPLE_INTERVAL_BYTES = 512 * 1024;
400
- var DEFAULT_STACK_DEPTH = 64;
401
- var MAX_SUMMARY_STACKS = 20;
402
- var MAX_FRAMES_PER_STACK = 12;
403
- function errorMessage(error) {
404
- return error instanceof Error ? error.message : String(error);
405
- }
406
- function numeric(value) {
407
- if (typeof value === "bigint") return Number(value);
408
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
409
- }
410
- function summarizePprof(profile) {
411
- const strings = profile.stringTable.strings;
412
- const functions = new Map(profile.function.map((entry) => [numeric(entry.id), entry]));
413
- const locations = new Map(profile.location.map((entry) => [numeric(entry.id), entry]));
414
- const stacks = /* @__PURE__ */ new Map();
415
- let sampledBytes = 0;
416
- let sampledObjects = 0;
417
- for (const sample of profile.sample) {
418
- const objects = numeric(sample.value[0]);
419
- const bytes = numeric(sample.value[1]);
420
- sampledObjects += objects;
421
- sampledBytes += bytes;
422
- const frames = sample.locationId.slice(0, MAX_FRAMES_PER_STACK).flatMap((locationId) => {
423
- const location = locations.get(numeric(locationId));
424
- const line = location?.line[0];
425
- const fn = line ? functions.get(numeric(line.functionId)) : void 0;
426
- if (!fn) return [];
427
- const name = strings[numeric(fn.name)] || "(anonymous)";
428
- const filename = strings[numeric(fn.filename)] || "";
429
- const lineNumber = numeric(line?.line);
430
- return [filename ? `${name} (${filename}${lineNumber > 0 ? `:${lineNumber}` : ""})` : name];
431
- });
432
- if (frames.length === 0) frames.push("(unknown)");
433
- const key = frames.join("\n");
434
- const existing = stacks.get(key);
435
- if (existing) {
436
- existing.bytes += bytes;
437
- existing.objects += objects;
438
- } else {
439
- stacks.set(key, { bytes, objects, frames });
440
- }
441
- }
442
- return {
443
- sampledBytes,
444
- sampledObjects,
445
- sampleCount: profile.sample.length,
446
- topStacks: [...stacks.values()].sort((left, right) => right.bytes - left.bytes).slice(0, MAX_SUMMARY_STACKS)
447
- };
448
- }
449
- async function startDatadogProfiler() {
450
- const pprof = await import("@datadog/pprof");
451
- pprof.heap.start(DEFAULT_SAMPLE_INTERVAL_BYTES, DEFAULT_STACK_DEPTH);
452
- let stopped = false;
453
- return {
454
- backend: "datadog-pprof",
455
- capture: async () => {
456
- if (stopped) throw new Error("Continuous memory profiler is stopped");
457
- const profile = pprof.heap.profile();
458
- return {
459
- backend: "datadog-pprof",
460
- data: await pprof.encode(profile),
461
- extension: ".pb.gz",
462
- summary: summarizePprof(profile)
463
- };
464
- },
465
- stop: async () => {
466
- if (stopped) return;
467
- stopped = true;
468
- pprof.heap.stop();
469
- }
470
- };
471
- }
472
- async function startInspectorProfiler() {
473
- const session = new Session();
474
- session.connect();
475
- try {
476
- await session.post("HeapProfiler.enable");
477
- await session.post("HeapProfiler.startSampling", {
478
- samplingInterval: DEFAULT_SAMPLE_INTERVAL_BYTES,
479
- includeObjectsCollectedByMajorGC: false,
480
- includeObjectsCollectedByMinorGC: false
481
- });
482
- } catch (error) {
483
- try {
484
- session.disconnect();
485
- } catch {
486
- }
487
- throw error;
488
- }
489
- let stopped = false;
490
- return {
491
- backend: "node-inspector",
492
- capture: async () => {
493
- if (stopped) throw new Error("Continuous memory profiler is stopped");
494
- const result = await session.post("HeapProfiler.stopSampling");
495
- await session.post("HeapProfiler.startSampling", {
496
- samplingInterval: DEFAULT_SAMPLE_INTERVAL_BYTES,
497
- includeObjectsCollectedByMajorGC: false,
498
- includeObjectsCollectedByMinorGC: false
499
- });
500
- return {
501
- backend: "node-inspector",
502
- data: JSON.stringify(result.profile),
503
- extension: ".heapprofile"
504
- };
505
- },
506
- stop: async () => {
507
- if (stopped) return;
508
- stopped = true;
509
- try {
510
- await session.post("HeapProfiler.disable");
511
- } catch {
512
- }
513
- try {
514
- session.disconnect();
515
- } catch {
516
- }
517
- }
518
- };
519
- }
520
- async function startContinuousMemoryProfiler() {
521
- try {
522
- return { profiler: await startDatadogProfiler() };
523
- } catch (error) {
524
- const fallbackReason = errorMessage(error);
525
- return {
526
- profiler: await startInspectorProfiler(),
527
- fallbackReason
528
- };
529
- }
530
- }
9
+ startSharedHeapWatchdog
10
+ } from "../chunk-DYLPMV4Q.js";
11
+ import {
12
+ EventBus
13
+ } from "../chunk-FHZVFYSL.js";
14
+ import {
15
+ MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS,
16
+ MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
17
+ SQLITE_MAILBOX_FILE,
18
+ SqliteMailbox,
19
+ encodeMailboxProjectServerMessage,
20
+ isMailboxProjectServerClientMessage,
21
+ mailboxProjectServerEndpoint,
22
+ mailboxProjectServerMetadataPath
23
+ } from "../chunk-PQNS3U7Z.js";
24
+ import {
25
+ redactMailboxCredential
26
+ } from "../chunk-IRUTEHCE.js";
27
+ import "../chunk-FWNLEZYG.js";
28
+ import {
29
+ restrictFilePermissions
30
+ } from "../chunk-6FHWXLPT.js";
31
+ import "../chunk-LX5QWLBL.js";
32
+ import "../chunk-POOOIWE5.js";
33
+ import {
34
+ atomicWrite
35
+ } from "../chunk-OSGLYW5J.js";
36
+ import "../chunk-SAFHACFA.js";
37
+ import "../chunk-25LSLC3G.js";
38
+ import "../chunk-TBRL2RCC.js";
531
39
 
532
- // src/utils/wstack-paths.ts
533
- import * as os from "node:os";
40
+ // src/coordination/mailbox-project-server.ts
41
+ import { randomBytes } from "node:crypto";
42
+ import * as fsPromises from "node:fs/promises";
43
+ import * as net from "node:net";
534
44
  import * as path from "node:path";
535
- function wstackGlobalRoot() {
536
- const fromEnv = process.env["WRONGSTACK_HOME"];
537
- if (fromEnv && fromEnv.trim().length > 0) return path.resolve(fromEnv);
538
- return path.join(os.homedir(), ".wrongstack");
539
- }
540
-
541
- // src/utils/memory-flight-recorder.ts
542
- var MB = 1024 * 1024;
543
- var HOUR_MS = 60 * 60 * 1e3;
544
- var DEFAULT_WINDOW_MS = 5 * 60 * 1e3;
545
- var DEFAULT_MINIMUM_WINDOW_MS = 2 * 60 * 1e3;
546
- var DEFAULT_COOLDOWN_MS = 15 * 60 * 1e3;
547
- var DEFAULT_CONTINUOUS_CAPTURE_MS = 5 * 60 * 1e3;
548
- var MAX_OBSERVATIONS = 24;
549
- var MAX_CAPTURE_SESSIONS = 12;
550
- var MAX_CAPTURES_PER_SESSION = 6;
551
- var SESSION_MAX_AGE_MS = 7 * 24 * HOUR_MS;
552
- function timestampSlug(date = /* @__PURE__ */ new Date()) {
553
- return date.toISOString().replaceAll(":", "").replaceAll(".", "-");
554
- }
555
- async function pruneCaptureFiles(artifactDir) {
556
- const entries = await fsp.readdir(artifactDir, { withFileTypes: true });
557
- const captures = entries.filter(
558
- (entry) => entry.isFile() && entry.name.startsWith("event-") && entry.name.endsWith(".json")
559
- ).map((entry) => entry.name).sort();
560
- const remove = captures.slice(0, Math.max(0, captures.length - MAX_CAPTURES_PER_SESSION));
561
- await Promise.all(
562
- remove.flatMap((eventName) => {
563
- const stem = eventName.slice("event-".length, -".json".length);
564
- return [
565
- fsp.rm(path2.join(artifactDir, eventName), { force: true }),
566
- fsp.rm(path2.join(artifactDir, `allocations-${stem}.heapprofile`), { force: true }),
567
- fsp.rm(path2.join(artifactDir, `allocations-${stem}.pb.gz`), { force: true })
568
- ];
569
- })
570
- );
571
- }
572
- async function pruneOldSessions(root, currentDir) {
573
- const entries = await fsp.readdir(root, { withFileTypes: true });
574
- const sessions = await Promise.all(
575
- entries.filter((entry) => entry.isDirectory() && entry.name !== path2.basename(currentDir)).map(async (entry) => {
576
- const fullPath = path2.join(root, entry.name);
577
- const stat2 = await fsp.stat(fullPath);
578
- return { fullPath, mtimeMs: stat2.mtimeMs };
579
- })
580
- );
581
- sessions.sort((a, b) => b.mtimeMs - a.mtimeMs);
582
- const now = Date.now();
583
- await Promise.all(
584
- sessions.filter(
585
- (session, index) => now - session.mtimeMs > SESSION_MAX_AGE_MS || index >= MAX_CAPTURE_SESSIONS && now - session.mtimeMs > 24 * HOUR_MS
586
- ).map((session) => fsp.rm(session.fullPath, { recursive: true, force: true }))
587
- );
588
- }
589
- var ContinuousMemoryCaptureWriter = class {
590
- artifactDir;
591
- root;
592
- ready;
593
- profiler;
594
- profilerBackend = "initializing";
595
- fallbackReason;
596
- startError;
597
- lastSummary;
598
- stopped = false;
599
- constructor(root) {
600
- this.root = root;
601
- this.artifactDir = path2.join(root, `${timestampSlug()}-pid-${process.pid}`);
602
- this.ready = this.start().catch((error) => {
603
- this.startError = error instanceof Error ? error.message : String(error);
604
- this.profilerBackend = "unavailable";
605
- });
606
- }
607
- async start() {
608
- const started = await startContinuousMemoryProfiler();
609
- this.profiler = started.profiler;
610
- this.profilerBackend = started.profiler.backend;
611
- this.fallbackReason = started.fallbackReason;
612
- }
613
- diagnostics() {
614
- return {
615
- memoryProfilerBackend: this.profilerBackend,
616
- ...this.fallbackReason ? { memoryProfilerFallbackReason: this.fallbackReason } : {},
617
- ...this.startError ? { memoryProfilerStartError: this.startError } : {},
618
- ...this.lastSummary ? {
619
- memoryProfileSampledBytes: this.lastSummary.sampledBytes,
620
- memoryProfileSampledObjects: this.lastSummary.sampledObjects,
621
- memoryProfileSampleCount: this.lastSummary.sampleCount,
622
- memoryProfileTopStack: this.lastSummary.topStack,
623
- memoryProfileTopStackBytes: this.lastSummary.topStackBytes
624
- } : {}
625
- };
626
- }
627
- async capture(event) {
628
- await this.ready;
629
- if (this.stopped) return;
630
- let profile;
631
- let captureError = this.startError;
632
- if (!captureError && this.profiler) {
633
- try {
634
- profile = await this.profiler.capture();
635
- const top = profile.summary?.topStacks[0];
636
- if (profile.summary) {
637
- this.lastSummary = {
638
- sampledBytes: profile.summary.sampledBytes,
639
- sampledObjects: profile.summary.sampledObjects,
640
- sampleCount: profile.summary.sampleCount,
641
- topStack: top?.frames.join(" <- "),
642
- topStackBytes: top?.bytes
643
- };
644
- }
645
- } catch (error) {
646
- captureError = error instanceof Error ? error.message : String(error);
647
- }
648
- }
649
- await fsp.mkdir(this.artifactDir, { recursive: true });
650
- const stem = `${timestampSlug(new Date(event.capturedAt))}-${event.reason}`;
651
- await fsp.writeFile(
652
- path2.join(this.artifactDir, `event-${stem}.json`),
653
- `${JSON.stringify(
654
- {
655
- ...event,
656
- profilerBackend: profile?.backend ?? this.profilerBackend,
657
- ...profile?.summary ? { allocationSummary: profile.summary } : {},
658
- ...this.fallbackReason ? { profilerFallbackReason: this.fallbackReason } : {},
659
- ...captureError ? { captureError } : {}
660
- },
661
- null,
662
- 2
663
- )}
664
- `,
665
- "utf8"
666
- );
667
- if (profile) {
668
- await fsp.writeFile(
669
- path2.join(this.artifactDir, `allocations-${stem}${profile.extension}`),
670
- profile.data
671
- );
672
- }
673
- await Promise.all([
674
- pruneCaptureFiles(this.artifactDir),
675
- fsp.mkdir(this.root, { recursive: true }).then(() => pruneOldSessions(this.root, this.artifactDir))
676
- ]);
677
- }
678
- async stop() {
679
- if (this.stopped) return;
680
- this.stopped = true;
681
- await this.ready;
682
- await this.profiler?.stop();
683
- }
684
- };
685
- function disabledWriter(artifactDir) {
686
- return {
687
- artifactDir,
688
- capture: async () => void 0,
689
- stop: async () => void 0
690
- };
691
- }
692
- function createMemoryFlightRecorder(options = {}) {
693
- const now = options.now ?? Date.now;
694
- const startedAt2 = now();
695
- const root = options.diagnosticsRoot ?? path2.join(wstackGlobalRoot(), "diagnostics", "memory");
696
- const isBunRuntime = typeof globalThis.Bun !== "undefined";
697
- const enabled = options.enabled ?? (!isBunRuntime && process.env["WRONGSTACK_MEMORY_FLIGHT_RECORDER"] !== "0" && process.env["NODE_ENV"] !== "test");
698
- const writer = options.captureWriter ?? (enabled ? new ContinuousMemoryCaptureWriter(root) : disabledWriter(path2.join(root, `${timestampSlug()}-pid-${process.pid}`)));
699
- const observations = [];
700
- const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
701
- const minimumWindowMs = options.minimumWindowMs ?? DEFAULT_MINIMUM_WINDOW_MS;
702
- const cooldownMs = options.cooldownMs ?? DEFAULT_COOLDOWN_MS;
703
- const continuousCaptureMs = options.continuousCaptureMs ?? DEFAULT_CONTINUOUS_CAPTURE_MS;
704
- const warmupMs = options.warmupMs ?? 6e4;
705
- let lastSignalCaptureAt = Number.NEGATIVE_INFINITY;
706
- let lastContinuousCaptureAt = startedAt2;
707
- let captureInFlight = false;
708
- let capturePromise;
709
- const observe = (sample, stats) => {
710
- const at = now();
711
- const postGcHeapUsed = typeof stats["postGcHeapUsed"] === "number" ? stats["postGcHeapUsed"] : 0;
712
- const postMajorGcHeapUsed = typeof stats["postMajorGcHeapUsed"] === "number" ? stats["postMajorGcHeapUsed"] : 0;
713
- const lastGcAgoMs = typeof stats["lastGcAgoMs"] === "number" ? stats["lastGcAgoMs"] : -1;
714
- const lastMajorGcAgoMs = typeof stats["lastMajorGcAgoMs"] === "number" ? stats["lastMajorGcAgoMs"] : -1;
715
- const retainedHeapUsed = postMajorGcHeapUsed > 0 && lastMajorGcAgoMs >= 0 && lastMajorGcAgoMs <= windowMs ? postMajorGcHeapUsed : postGcHeapUsed > 0 && lastGcAgoMs >= 0 && lastGcAgoMs <= minimumWindowMs ? postGcHeapUsed : sample.heapUsed;
716
- observations.push({
717
- at,
718
- heapUsed: retainedHeapUsed,
719
- rss: sample.rss,
720
- nativeResidual: sample.nativeResidual ?? 0
721
- });
722
- while (observations.length > MAX_OBSERVATIONS || observations.length > 1 && at - (observations[0]?.at ?? at) > windowMs) {
723
- observations.shift();
724
- }
725
- const first = observations[0] ?? observations.at(-1);
726
- const elapsedMs = first ? Math.max(0, at - first.at) : 0;
727
- const heapGrowthBytes = first ? retainedHeapUsed - first.heapUsed : 0;
728
- const rssGrowthBytes = first ? sample.rss - first.rss : 0;
729
- const nativeGrowthBytes = first ? (sample.nativeResidual ?? 0) - first.nativeResidual : 0;
730
- const heapGrowthBytesPerHour = elapsedMs > 0 ? Math.round(heapGrowthBytes / elapsedMs * HOUR_MS) : 0;
731
- const rssGrowthBytesPerHour = elapsedMs > 0 ? Math.round(rssGrowthBytes / elapsedMs * HOUR_MS) : 0;
732
- const nativeGrowthBytesPerHour = elapsedMs > 0 ? Math.round(nativeGrowthBytes / elapsedMs * HOUR_MS) : 0;
733
- let signal = "stable";
734
- if ((sample.detachedContexts ?? 0) > 0) signal = "detached-contexts";
735
- else if (heapGrowthBytes >= 32 * MB && heapGrowthBytesPerHour > 0) signal = "js-retention";
736
- else if (nativeGrowthBytes >= 64 * MB && nativeGrowthBytesPerHour > 0) signal = "native-rss";
737
- const diagnosis = {
738
- memorySignal: signal,
739
- memoryWindowMs: elapsedMs,
740
- retainedHeapUsed,
741
- transientHeapBytes: Math.max(0, sample.heapUsed - retainedHeapUsed),
742
- heapGrowthBytes,
743
- rssGrowthBytes,
744
- nativeGrowthBytes,
745
- heapGrowthBytesPerHour,
746
- rssGrowthBytesPerHour,
747
- nativeGrowthBytesPerHour,
748
- memoryArtifactDir: writer.artifactDir,
749
- ...writer.diagnostics?.() ?? {}
750
- };
751
- let reason;
752
- if (at - startedAt2 >= warmupMs) {
753
- if ((sample.detachedContexts ?? 0) > 0) reason = "detached-contexts";
754
- else if (sample.load >= 0.45) reason = "heap-load";
755
- else if (sample.heapUsed >= (options.absoluteHeapBytes ?? 768 * MB)) {
756
- reason = "absolute-heap";
757
- } else if (elapsedMs >= minimumWindowMs && heapGrowthBytes >= (options.heapGrowthBytes ?? 32 * MB) && heapGrowthBytesPerHour >= (options.heapGrowthBytesPerHour ?? 192 * MB)) {
758
- reason = "heap-growth";
759
- } else if (elapsedMs >= minimumWindowMs && nativeGrowthBytes >= (options.nativeGrowthBytes ?? 128 * MB) && nativeGrowthBytesPerHour >= (options.nativeGrowthBytesPerHour ?? 256 * MB)) {
760
- reason = "native-rss-growth";
761
- }
762
- }
763
- const signalCaptureDue = reason !== void 0 && at - lastSignalCaptureAt >= cooldownMs;
764
- const intervalCaptureDue = at - lastContinuousCaptureAt >= continuousCaptureMs;
765
- const captureReason = signalCaptureDue ? reason : intervalCaptureDue ? "interval" : void 0;
766
- if (enabled && captureReason && !captureInFlight) {
767
- if (captureReason !== "interval") lastSignalCaptureAt = at;
768
- lastContinuousCaptureAt = at;
769
- captureInFlight = true;
770
- diagnosis["memoryCaptureReason"] = captureReason;
771
- capturePromise = writer.capture({
772
- reason: captureReason,
773
- capturedAt: new Date(at).toISOString(),
774
- pid: process.pid,
775
- sample,
776
- stats,
777
- diagnosis: { ...diagnosis },
778
- observations: observations.map((observation) => ({ ...observation }))
779
- }).catch(() => {
780
- }).finally(() => {
781
- captureInFlight = false;
782
- capturePromise = void 0;
783
- });
784
- }
785
- return diagnosis;
786
- };
787
- return {
788
- artifactDir: writer.artifactDir,
789
- observe,
790
- stop: async () => {
791
- await capturePromise;
792
- await writer.stop();
793
- }
794
- };
795
- }
796
-
797
- // src/utils/heap-watchdog.ts
798
- var MB2 = 1024 * 1024;
799
- function defaultHeapLogPath() {
800
- const override = process.env["WRONGSTACK_HEAP_LOG_PATH"]?.trim();
801
- if (override) return path3.resolve(override);
802
- return path3.join(wstackGlobalRoot(), "logs", "heap.jsonl");
803
- }
804
- function takeHeapSample() {
805
- const m = process.memoryUsage();
806
- const heap = v8.getHeapStatistics();
807
- let spaces = [];
808
- try {
809
- spaces = v8.getHeapSpaceStatistics();
810
- } catch {
811
- }
812
- const spaceUsed = (name) => spaces.find((space) => space.space_name === name)?.space_used_size ?? 0;
813
- const limit = heap.heap_size_limit || 0;
814
- let activeResources = 0;
815
- let activeResourceTypes = "";
816
- try {
817
- const resources = process.getActiveResourcesInfo?.() ?? [];
818
- activeResources = resources.length;
819
- const counts = /* @__PURE__ */ new Map();
820
- for (const resource of resources) counts.set(resource, (counts.get(resource) ?? 0) + 1);
821
- activeResourceTypes = [...counts].sort(([left], [right]) => left.localeCompare(right)).map(([resource, count]) => `${resource}=${count}`).join(",");
822
- } catch {
823
- }
824
- return {
825
- ts: (/* @__PURE__ */ new Date()).toISOString(),
826
- rss: m.rss,
827
- heapUsed: m.heapUsed,
828
- heapTotal: m.heapTotal,
829
- external: m.external,
830
- arrayBuffers: m.arrayBuffers,
831
- nativeResidual: Math.max(0, m.rss - m.heapTotal - m.external),
832
- oldSpaceUsed: spaceUsed("old_space"),
833
- newSpaceUsed: spaceUsed("new_space"),
834
- largeObjectSpaceUsed: spaceUsed("large_object_space"),
835
- codeSpaceUsed: spaceUsed("code_space"),
836
- mallocedMemory: heap.malloced_memory,
837
- peakMallocedMemory: heap.peak_malloced_memory,
838
- nativeContexts: heap.number_of_native_contexts,
839
- detachedContexts: heap.number_of_detached_contexts,
840
- activeResources,
841
- activeResourceTypes,
842
- heapLimit: limit,
843
- load: limit > 0 ? m.heapUsed / limit : 0
844
- };
845
- }
846
- function startHeapWatchdog(opts = {}) {
847
- const profileMode = process.env["WRONGSTACK_MEMORY_PROFILE"] === "1";
848
- const sampleEveryMs = opts.sampleEveryMs ?? (profileMode ? 5e3 : 3e4);
849
- const logEveryMs = opts.logEveryMs ?? (profileMode ? 5e3 : 6e4);
850
- const logPath = opts.logPath ?? defaultHeapLogPath();
851
- const warnAt = opts.warnAt ?? 0.6;
852
- const criticalAt = opts.criticalAt ?? 0.85;
853
- const REARM_MARGIN = 0.05;
854
- let warnArmed = true;
855
- let criticalArmed = true;
856
- let lastLogAt = 0;
857
- let writeInFlight = false;
858
- let pendingLine;
859
- let drainPromise;
860
- let stopped = false;
861
- let dirReady = false;
862
- const flightRecorder = createMemoryFlightRecorder();
863
- let gcMajorCount = 0;
864
- let gcMinorCount = 0;
865
- let gcIncrementalCount = 0;
866
- let gcWeakCallbackCount = 0;
867
- let gcDurationMs = 0;
868
- let postGcHeapUsed = 0;
869
- let postMajorGcHeapUsed = 0;
870
- let lastGcAt = 0;
871
- let lastMajorGcAt = 0;
872
- const gcObserver = new PerformanceObserver((list) => {
873
- for (const entry of list.getEntries()) {
874
- const kind = entry.detail?.kind ?? 0;
875
- if ((kind & performanceConstants.NODE_PERFORMANCE_GC_MAJOR) !== 0) gcMajorCount++;
876
- if ((kind & performanceConstants.NODE_PERFORMANCE_GC_MINOR) !== 0) gcMinorCount++;
877
- if ((kind & performanceConstants.NODE_PERFORMANCE_GC_INCREMENTAL) !== 0) {
878
- gcIncrementalCount++;
879
- }
880
- if ((kind & performanceConstants.NODE_PERFORMANCE_GC_WEAKCB) !== 0) gcWeakCallbackCount++;
881
- gcDurationMs += entry.duration;
882
- postGcHeapUsed = process.memoryUsage().heapUsed;
883
- if ((kind & performanceConstants.NODE_PERFORMANCE_GC_MAJOR) !== 0) {
884
- postMajorGcHeapUsed = postGcHeapUsed;
885
- lastMajorGcAt = Date.now();
886
- }
887
- lastGcAt = Date.now();
888
- }
889
- });
890
- try {
891
- gcObserver.observe({ entryTypes: ["gc"] });
892
- } catch {
893
- }
894
- const collectRuntimeStats = () => ({
895
- gcMajorCount,
896
- gcMinorCount,
897
- gcIncrementalCount,
898
- gcWeakCallbackCount,
899
- gcDurationMs: Math.round(gcDurationMs),
900
- postGcHeapUsed,
901
- postMajorGcHeapUsed,
902
- lastGcAgoMs: lastGcAt > 0 ? Date.now() - lastGcAt : -1,
903
- lastMajorGcAgoMs: lastMajorGcAt > 0 ? Date.now() - lastMajorGcAt : -1
904
- });
905
- const writeDiagnosticLine = opts.writeDiagnosticLine ?? (async (targetPath, line) => {
906
- if (!dirReady) {
907
- await fsp2.mkdir(path3.dirname(targetPath), { recursive: true });
908
- dirReady = true;
909
- }
910
- await fsp2.appendFile(targetPath, `${line}
911
- `, { encoding: "utf8", mode: 384 });
912
- });
913
- const drainWrites = async (firstLine) => {
914
- let line = firstLine;
915
- while (line !== void 0) {
916
- try {
917
- await writeDiagnosticLine(logPath, line);
918
- } catch {
919
- }
920
- line = pendingLine;
921
- pendingLine = void 0;
922
- }
923
- writeInFlight = false;
924
- };
925
- const append = (line) => {
926
- if (stopped) return;
927
- if (writeInFlight) {
928
- pendingLine = line;
929
- return;
930
- }
931
- writeInFlight = true;
932
- drainPromise = drainWrites(line);
933
- };
934
- const appendTerminalSample = (event, error, origin) => {
935
- if (stopped) return;
936
- let extras = {};
937
- try {
938
- extras = opts.collectStats?.() ?? {};
939
- } catch {
940
- }
941
- try {
942
- const sample = takeHeapSample();
943
- const runtimeStats = collectRuntimeStats();
944
- fs.mkdirSync(path3.dirname(logPath), { recursive: true });
945
- fs.appendFileSync(
946
- logPath,
947
- `${JSON.stringify({
948
- ...extras,
949
- ...runtimeStats,
950
- event,
951
- ...origin ? { crashOrigin: origin } : {},
952
- ...error instanceof Error ? { errorName: error.name, errorMessage: error.message.slice(0, 1e3) } : {},
953
- pid: process.pid,
954
- ...sample,
955
- exitCode: process.exitCode
956
- })}
957
- `,
958
- "utf8"
959
- );
960
- } catch {
961
- }
962
- };
963
- const onUncaughtException = (error, origin) => {
964
- appendTerminalSample("memory.flight_recorder.crash", error, origin);
965
- };
966
- const onProcessExit = () => {
967
- appendTerminalSample("memory.flight_recorder.exit");
968
- };
969
- process.on("uncaughtExceptionMonitor", onUncaughtException);
970
- process.on("exit", onProcessExit);
971
- const tick = () => {
972
- let userTimings = 0;
973
- try {
974
- userTimings = performance.getEntriesByType("mark").length + performance.getEntriesByType("measure").length;
975
- if (userTimings > 0) {
976
- performance.clearMarks();
977
- performance.clearMeasures();
978
- }
979
- } catch {
980
- }
981
- const s = takeHeapSample();
982
- let extras = {};
983
- try {
984
- extras = opts.collectStats?.() ?? {};
985
- } catch {
986
- }
987
- const runtimeStats = collectRuntimeStats();
988
- const diagnosticStats = { ...extras, ...runtimeStats };
989
- const memoryDiagnosis = flightRecorder.observe(s, diagnosticStats);
990
- if (s.load >= criticalAt && criticalArmed) {
991
- criticalArmed = false;
992
- warnArmed = false;
993
- opts.onWarn?.(
994
- "critical",
995
- `Heap critical: ${Math.round(s.heapUsed / MB2)} MB of ~${Math.round(s.heapLimit / MB2)} MB V8 limit (${Math.round(s.load * 100)}%). An out-of-memory crash is likely soon \u2014 finish/checkpoint work and restart the session. Diagnostics: ${logPath}`,
996
- s
997
- );
998
- } else if (s.load >= warnAt && warnArmed) {
999
- warnArmed = false;
1000
- opts.onWarn?.(
1001
- "warn",
1002
- `Heap high: ${Math.round(s.heapUsed / MB2)} MB of ~${Math.round(s.heapLimit / MB2)} MB V8 limit (${Math.round(s.load * 100)}%). Memory diagnostics are being recorded to ${logPath}`,
1003
- s
1004
- );
1005
- }
1006
- if (!warnArmed && s.load < warnAt - REARM_MARGIN) warnArmed = true;
1007
- if (!criticalArmed && s.load < criticalAt - REARM_MARGIN) criticalArmed = true;
1008
- const due = Date.now() - lastLogAt >= logEveryMs;
1009
- const crossed = s.load >= warnAt;
1010
- const captured = typeof memoryDiagnosis["memoryCaptureReason"] === "string";
1011
- if (due || crossed || captured) {
1012
- lastLogAt = Date.now();
1013
- append(
1014
- JSON.stringify({
1015
- ...extras,
1016
- ...runtimeStats,
1017
- ...memoryDiagnosis,
1018
- pid: process.pid,
1019
- ...s,
1020
- userTimings
1021
- })
1022
- );
1023
- }
1024
- };
1025
- const timer = setInterval(tick, sampleEveryMs);
1026
- timer.unref?.();
1027
- tick();
1028
- return async () => {
1029
- stopped = true;
1030
- clearInterval(timer);
1031
- gcObserver.disconnect();
1032
- process.removeListener("uncaughtExceptionMonitor", onUncaughtException);
1033
- process.removeListener("exit", onProcessExit);
1034
- await drainPromise;
1035
- await flightRecorder.stop();
1036
- };
1037
- }
1038
- var sharedContributors = /* @__PURE__ */ new Map();
1039
- var nextSharedContributorId = 1;
1040
- var sharedSamplerActive = false;
1041
- var stopSharedSampler;
1042
- function collectSharedStats() {
1043
- const merged = {};
1044
- for (const contributor of sharedContributors.values()) {
1045
- try {
1046
- Object.assign(merged, contributor.collectStats?.() ?? {});
1047
- } catch {
1048
- }
1049
- }
1050
- return merged;
1051
- }
1052
- function warnSharedContributors(level, message, sample) {
1053
- for (const contributor of sharedContributors.values()) {
1054
- try {
1055
- contributor.onWarn?.(level, message, sample);
1056
- } catch {
1057
- }
1058
- }
1059
- }
1060
- function startSharedHeapWatchdog(opts = {}) {
1061
- if (process.env["NODE_ENV"] === "test" && !sharedSamplerActive && opts.logPath === void 0 && opts.writeDiagnosticLine === void 0) {
1062
- return async () => void 0;
1063
- }
1064
- const id = nextSharedContributorId++;
1065
- sharedContributors.set(id, {
1066
- collectStats: opts.collectStats,
1067
- onWarn: opts.onWarn
1068
- });
1069
- if (!sharedSamplerActive) {
1070
- sharedSamplerActive = true;
1071
- stopSharedSampler = startHeapWatchdog({
1072
- ...opts,
1073
- collectStats: collectSharedStats,
1074
- onWarn: warnSharedContributors
1075
- });
1076
- }
1077
- let stopped = false;
1078
- return async () => {
1079
- if (stopped) return;
1080
- stopped = true;
1081
- sharedContributors.delete(id);
1082
- if (sharedContributors.size > 0 || !sharedSamplerActive) return;
1083
- const stop2 = stopSharedSampler;
1084
- if (!stop2) return;
1085
- sharedSamplerActive = false;
1086
- stopSharedSampler = void 0;
1087
- await stop2();
1088
- };
1089
- }
1090
-
1091
- // src/utils/perf-profile.ts
1092
- var daemonDefaults = false;
1093
- function useDaemonPerfDefaults() {
1094
- daemonDefaults = true;
1095
- }
1096
-
1097
- // src/coordination/mailbox-credential-store.ts
1098
- import * as crypto from "node:crypto";
1099
- var CREDENTIAL_STORE_FILE = "_mailbox_credentials.json";
1100
- var MAX_CREDENTIAL_TTL = {
1101
- agent: 7 * 24 * 60 * 60 * 1e3,
1102
- // 7 days
1103
- operator: 24 * 60 * 60 * 1e3,
1104
- // 24 hours
1105
- service: 30 * 24 * 60 * 60 * 1e3
1106
- // 30 days
1107
- };
1108
- var ROTATION_OVERLAP_MS = 60 * 60 * 1e3;
1109
- function createMailboxCredential(opts, now = Date.now()) {
1110
- const ttlMs = Math.min(opts.ttlMs, MAX_CREDENTIAL_TTL[opts.kind]);
1111
- const credentialId = crypto.randomUUID();
1112
- const secret = crypto.randomBytes(32).toString("hex");
1113
- const verifierKey = crypto.createHash("sha256").update(secret).digest();
1114
- const verifier = crypto.createHmac("sha256", verifierKey).update(credentialId).digest("hex");
1115
- return {
1116
- credential: {
1117
- credentialId,
1118
- verifier,
1119
- verifierAlgorithm: "hmac-sha256",
1120
- principalId: opts.principalId,
1121
- projectId: opts.projectId,
1122
- capabilities: opts.capabilities,
1123
- kind: opts.kind,
1124
- issuedAt: new Date(now).toISOString(),
1125
- expiresAt: new Date(now + ttlMs).toISOString(),
1126
- notBefore: opts.notBefore?.toISOString(),
1127
- status: "active",
1128
- statusChangedAt: new Date(now).toISOString(),
1129
- supersedes: opts.supersedes,
1130
- lastModifiedBy: opts.issuedBy
1131
- },
1132
- secret
1133
- };
1134
- }
1135
- function redactMailboxCredential(credential) {
1136
- const { verifier: _verifier, verifierAlgorithm: _algorithm, ...rest } = credential;
1137
- return rest;
1138
- }
1139
- function verifyMailboxCredential(credential, secret, now = Date.now()) {
1140
- if (credential === void 0) {
1141
- return { valid: false, reason: "credential not found" };
1142
- }
1143
- const rotationStillValid = credential.status === "rotated_out" && credential.rotationValidUntil !== void 0 && new Date(credential.rotationValidUntil).getTime() >= now;
1144
- if (credential.status !== "active" && !rotationStillValid) {
1145
- return { valid: false, reason: `credential is ${credential.status}`, credential };
1146
- }
1147
- if (new Date(credential.expiresAt).getTime() <= now) {
1148
- return { valid: false, reason: "credential expired", credential };
1149
- }
1150
- if (credential.notBefore !== void 0 && new Date(credential.notBefore).getTime() > now) {
1151
- return { valid: false, reason: "credential not yet valid", credential };
1152
- }
1153
- const verifierKey = crypto.createHash("sha256").update(secret).digest();
1154
- const expected = crypto.createHmac("sha256", verifierKey).update(credential.credentialId).digest("hex");
1155
- const actualBytes = Buffer.from(credential.verifier, "hex");
1156
- const expectedBytes = Buffer.from(expected, "hex");
1157
- if (actualBytes.length !== expectedBytes.length || !crypto.timingSafeEqual(actualBytes, expectedBytes)) {
1158
- return { valid: false, reason: "invalid secret" };
1159
- }
1160
- return { valid: true, credential };
1161
- }
1162
-
1163
- // src/coordination/mailbox-events.ts
1164
- var MailboxEventEmitter = class {
1165
- listeners = /* @__PURE__ */ new Set();
1166
- subscribe(fn) {
1167
- this.listeners.add(fn);
1168
- return () => {
1169
- this.listeners.delete(fn);
1170
- };
1171
- }
1172
- emit(event) {
1173
- const snapshot = [...this.listeners];
1174
- for (const fn of snapshot) {
1175
- try {
1176
- fn(event);
1177
- } catch {
1178
- }
1179
- }
1180
- }
1181
- /** Number of active subscribers (for observability / metrics). */
1182
- get subscriberCount() {
1183
- return this.listeners.size;
1184
- }
1185
- clear() {
1186
- this.listeners.clear();
1187
- }
1188
- };
1189
-
1190
- // src/coordination/mailbox-project-server-endpoint.ts
1191
- import { createHash as createHash2 } from "node:crypto";
1192
- import * as os2 from "node:os";
1193
- import * as path4 from "node:path";
1194
-
1195
- // src/coordination/mailbox-project-server-protocol.ts
1196
- var MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION = 4;
1197
- var MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS = 16 * 1024 * 1024;
1198
- var MAILBOX_SERVER_OPERATION_NAMES = {
1199
- ping: true,
1200
- send: true,
1201
- sendRuntimeControl: true,
1202
- query: true,
1203
- ack: true,
1204
- ackMany: true,
1205
- unreadCount: true,
1206
- softDelete: true,
1207
- restore: true,
1208
- registerAgent: true,
1209
- deregisterAgent: true,
1210
- heartbeat: true,
1211
- getAgentStatuses: true,
1212
- getOnlineAgents: true,
1213
- purgeAgents: true,
1214
- registerClient: true,
1215
- deregisterClient: true,
1216
- clientHeartbeat: true,
1217
- getClientStatuses: true,
1218
- purgeClients: true,
1219
- clearAll: true,
1220
- purgeStale: true,
1221
- autoCompact: true,
1222
- credentialIssue: true,
1223
- credentialVerify: true,
1224
- credentialRevoke: true,
1225
- credentialRotate: true,
1226
- credentialGet: true,
1227
- credentialList: true,
1228
- credentialStatusCounts: true
1229
- };
1230
- function isRequestId(value) {
1231
- return Number.isSafeInteger(value) && value >= 0;
1232
- }
1233
- function isRecord(value) {
1234
- return typeof value === "object" && value !== null && !Array.isArray(value);
1235
- }
1236
- function hasString(record, key) {
1237
- return typeof record[key] === "string" && record[key].length > 0;
1238
- }
1239
- function hasRecord(record, key) {
1240
- return isRecord(record[key]);
1241
- }
1242
- function isMailboxServerOperationArgs(op, value) {
1243
- if (!isRecord(value)) return false;
1244
- switch (op) {
1245
- case "ping":
1246
- case "getAgentStatuses":
1247
- case "getOnlineAgents":
1248
- case "getClientStatuses":
1249
- case "purgeClients":
1250
- case "clearAll":
1251
- case "credentialList":
1252
- case "credentialStatusCounts":
1253
- return true;
1254
- case "send":
1255
- case "sendRuntimeControl":
1256
- case "ack":
1257
- case "ackMany":
1258
- case "registerAgent":
1259
- case "heartbeat":
1260
- case "registerClient":
1261
- case "clientHeartbeat":
1262
- return hasRecord(value, "input");
1263
- case "query":
1264
- return hasRecord(value, "query");
1265
- case "unreadCount":
1266
- return hasString(value, "forAgentId");
1267
- case "softDelete":
1268
- return hasString(value, "mailId") && hasString(value, "by");
1269
- case "restore":
1270
- return hasString(value, "mailId");
1271
- case "deregisterAgent":
1272
- return hasString(value, "agentId");
1273
- case "purgeAgents":
1274
- case "purgeStale":
1275
- case "autoCompact":
1276
- return true;
1277
- case "deregisterClient":
1278
- return hasString(value, "clientId");
1279
- case "credentialIssue":
1280
- return hasRecord(value, "options");
1281
- case "credentialVerify":
1282
- return hasString(value, "credentialId") && hasString(value, "secret");
1283
- case "credentialRevoke":
1284
- case "credentialRotate":
1285
- case "credentialGet":
1286
- return hasString(value, "credentialId");
1287
- }
1288
- }
1289
- function isMailboxProjectServerClientMessage(value) {
1290
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1291
- const message = value;
1292
- if (message["type"] === "heartbeat") return true;
1293
- if (message["type"] === "shutdown") {
1294
- return isRequestId(message["id"]) && (message["reason"] === void 0 || typeof message["reason"] === "string");
1295
- }
1296
- if (message["type"] !== "request" || !isRequestId(message["id"])) return false;
1297
- const op = message["op"];
1298
- return typeof op === "string" && Object.hasOwn(MAILBOX_SERVER_OPERATION_NAMES, op) && isMailboxServerOperationArgs(op, message["args"]);
1299
- }
1300
- function encodeMailboxProjectServerMessage(message) {
1301
- return `${JSON.stringify(message)}
1302
- `;
1303
- }
1304
-
1305
- // src/coordination/mailbox-project-server-endpoint.ts
1306
- var MAILBOX_PROJECT_SERVER_METADATA_FILE = ".mailbox-server.json";
1307
- function normalizeLocalPath(value) {
1308
- const resolved = path4.resolve(value);
1309
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
1310
- }
1311
- function mailboxProjectServerKey(projectDir2) {
1312
- return createHash2("sha256").update(normalizeLocalPath(projectDir2)).digest("hex").slice(0, 24);
1313
- }
1314
- function mailboxProjectServerEndpoint(projectDir2) {
1315
- const key = mailboxProjectServerKey(projectDir2);
1316
- if (process.platform === "win32") {
1317
- return `\\\\.\\pipe\\wrongstack-mailbox-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}-${key}`;
1318
- }
1319
- return path4.join(
1320
- os2.tmpdir(),
1321
- `wsmb-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}`,
1322
- `${key}.sock`
1323
- );
1324
- }
1325
- function mailboxProjectServerMetadataPath(projectDir2) {
1326
- return path4.join(path4.resolve(projectDir2), MAILBOX_PROJECT_SERVER_METADATA_FILE);
1327
- }
1328
-
1329
- // src/coordination/sqlite-mailbox.ts
1330
- import { randomUUID as randomUUID2 } from "node:crypto";
1331
- import * as fs3 from "node:fs";
1332
- import * as path6 from "node:path";
1333
-
1334
- // src/coordination/global-mailbox-completion.ts
1335
- function isMailboxMessageProjection(msg) {
1336
- if (!("recipientState" in msg)) return false;
1337
- const recipientState = msg.recipientState;
1338
- return typeof recipientState === "object" && recipientState !== null && !Array.isArray(recipientState);
1339
- }
1340
- function isMessageCompletedForActor(msg, actorId) {
1341
- if (!isMailboxMessageProjection(msg)) return msg.completed === true;
1342
- if (msg.legacyGlobalCompletion) return true;
1343
- if (actorId !== void 0) {
1344
- const state = msg.recipientState[actorId];
1345
- if (state !== void 0) return state.completedAt !== void 0;
1346
- if (Object.keys(msg.recipientState).length > 0) return false;
1347
- }
1348
- return msg.completed === true;
1349
- }
1350
-
1351
- // src/coordination/mailbox-constants.ts
1352
- var AGENT_STALE_MS = 6e4;
1353
- var CLIENT_STALE_MS = 6e4;
1354
- var HEARTBEAT_THROTTLE_MS = 5e3;
1355
- var LINE_SEPARATOR = "\n";
1356
- var AUTO_COMPACT_INTERVAL_MS = 3e5;
1357
- var AUTO_COMPACT_READ_MAX_AGE_MS = 6e5;
1358
- var AUTO_COMPACT_DEFAULT_TTL_MS = 864e5;
1359
- var AUTO_COMPACT_TYPE_TTL_MS = {
1360
- status: 18e5
1361
- // 30 min
1362
- };
1363
-
1364
- // src/coordination/mailbox-predicates.ts
1365
- function mailboxIdentityBase(agentId) {
1366
- return agentId.split(/[@#]/, 1)[0].trim().toLowerCase();
1367
- }
1368
- function isMailboxLeader(agentId, role) {
1369
- return mailboxIdentityBase(agentId) === "leader" || role?.trim().toLowerCase() === "leader";
1370
- }
1371
- function isMailboxMessageVisibleTo(message, agentId, role) {
1372
- return message.audience !== "leaders" || isMailboxLeader(agentId, role);
1373
- }
1374
- function validateSendType(type, to) {
1375
- if (type === "control") {
1376
- throw new TypeError('Type "control" is reserved for runtime use and cannot be set by agents');
1377
- }
1378
- const isMultiRecipient = to === "*" || to.startsWith("@session:");
1379
- if (type === "assign" && isMultiRecipient) {
1380
- throw new TypeError(
1381
- `Type "assign" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
1382
- );
1383
- }
1384
- if (type === "steer" && isMultiRecipient) {
1385
- throw new TypeError(
1386
- `Type "steer" requires a specific recipient \u2014 multi-recipient target "${to}" is ambiguous`
1387
- );
1388
- }
1389
- }
1390
- var SESSION_RECIPIENT_PREFIX = "@session:";
1391
- function sessionRecipient(sessionId) {
1392
- const normalizedSessionId = sessionId.trim();
1393
- if (!normalizedSessionId) {
1394
- throw new TypeError('sessionId is required for the "@session" recipient');
1395
- }
1396
- return `${SESSION_RECIPIENT_PREFIX}${normalizedSessionId}`;
1397
- }
1398
- function normalizeRecipient(to, sessionId) {
1399
- const trimmed = to.trim();
1400
- const normalized = trimmed.toLowerCase();
1401
- if (normalized === "all") return "*";
1402
- if (normalized === "@session") return sessionRecipient(sessionId ?? "");
1403
- return trimmed;
1404
- }
1405
- function isMailboxReceiptRecordV2(value) {
1406
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1407
- const v = value;
1408
- if (v["__mailboxReceipt"] !== 2) return false;
1409
- if (typeof v["messageId"] !== "string" || v["messageId"].length === 0) return false;
1410
- if (typeof v["actorId"] !== "string" || v["actorId"].length === 0) return false;
1411
- if (typeof v["timestamp"] !== "string" || v["timestamp"].length === 0) return false;
1412
- if ("read" in v && typeof v["read"] !== "boolean") return false;
1413
- if ("completed" in v && typeof v["completed"] !== "boolean") return false;
1414
- if ("outcome" in v && v["outcome"] !== void 0 && typeof v["outcome"] !== "string")
1415
- return false;
1416
- return true;
1417
- }
1418
-
1419
- // src/coordination/mailbox-session-sync.ts
1420
- function isAffectedBySessionAffinity(message) {
1421
- return message.sessionAffinity !== void 0;
1422
- }
1423
- async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
1424
- if (!isAffectedBySessionAffinity(message)) return true;
1425
- const affinity = message.sessionAffinity;
1426
- if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
1427
- return false;
1428
- }
1429
- if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
1430
- return false;
1431
- }
1432
- if (!currentSessionId) {
1433
- return ctx?.allowUnscoped === true;
1434
- }
1435
- if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
1436
- if (affinity.sessionId !== currentSessionId) return false;
1437
- return true;
1438
- }
1439
- if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
1440
- try {
1441
- const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
1442
- if (resolved === currentSessionId) return true;
1443
- if (resolved !== void 0) return false;
1444
- } catch {
1445
- }
1446
- }
1447
- if (ctx?.allowUnscoped === true) return true;
1448
- return false;
1449
- }
1450
-
1451
- // src/coordination/mailbox-message-codec.ts
1452
- var MESSAGE_TYPES = /* @__PURE__ */ new Set([
1453
- "note",
1454
- "ask",
1455
- "assign",
1456
- "steer",
1457
- "btw",
1458
- "broadcast",
1459
- "status",
1460
- "result",
1461
- "review",
1462
- "control"
1463
- ]);
1464
- var PRIORITIES = /* @__PURE__ */ new Set(["low", "normal", "high"]);
1465
- var AUDIENCES = /* @__PURE__ */ new Set(["all", "leaders"]);
1466
- var TASK_STATUSES = /* @__PURE__ */ new Set([
1467
- "pending",
1468
- "in_progress",
1469
- "completed",
1470
- "failed",
1471
- "idle",
1472
- "running",
1473
- "streaming",
1474
- "waiting_user",
1475
- "error",
1476
- "offline",
1477
- "busy"
1478
- ]);
1479
- function isRecord2(value) {
1480
- return typeof value === "object" && value !== null && !Array.isArray(value);
1481
- }
1482
- function requiredString(record, key) {
1483
- const value = record[key];
1484
- if (typeof value !== "string") {
1485
- throw new TypeError(`mailbox message field "${key}" must be a string`);
1486
- }
1487
- return value;
1488
- }
1489
- function optionalString(record, key) {
1490
- const value = record[key];
1491
- if (value === void 0) return {};
1492
- if (typeof value !== "string") {
1493
- throw new TypeError(`mailbox message field "${key}" must be a string when present`);
1494
- }
1495
- return { [key]: value };
1496
- }
1497
- function parseMessageType(value) {
1498
- if (value === "info") return "note";
1499
- if (value === "task") return "assign";
1500
- if (typeof value === "string" && MESSAGE_TYPES.has(value)) {
1501
- return value;
1502
- }
1503
- throw new TypeError('mailbox message field "type" is invalid');
1504
- }
1505
- function normalizeMailboxMessageType(value) {
1506
- return parseMessageType(value);
1507
- }
1508
- function parsePriority(value) {
1509
- if (typeof value === "string" && PRIORITIES.has(value)) {
1510
- return value;
1511
- }
1512
- if (typeof value === "string") return "normal";
1513
- throw new TypeError('mailbox message field "priority" must be a string');
1514
- }
1515
- function parseAudience(value) {
1516
- if (value === void 0 || value === "all") return void 0;
1517
- if (typeof value === "string" && AUDIENCES.has(value)) {
1518
- return value;
1519
- }
1520
- throw new TypeError('mailbox message field "audience" is invalid');
1521
- }
1522
- function parseReadReceipts(record, to) {
1523
- const value = record["readBy"];
1524
- if (value === void 0) {
1525
- const legacyReadAt = record["readAt"];
1526
- return record["read"] === true && typeof legacyReadAt === "string" ? { [to || "unknown"]: legacyReadAt } : {};
1527
- }
1528
- if (!isRecord2(value)) {
1529
- throw new TypeError('mailbox message field "readBy" must be an object');
1530
- }
1531
- const receipts = {};
1532
- for (const [agentId, timestamp] of Object.entries(value)) {
1533
- if (typeof timestamp !== "string") {
1534
- throw new TypeError("mailbox message read receipt timestamps must be strings");
1535
- }
1536
- receipts[agentId] = timestamp;
1537
- }
1538
- return receipts;
1539
- }
1540
- function parseTaskContext(value) {
1541
- if (value === void 0) return void 0;
1542
- if (!isRecord2(value)) {
1543
- throw new TypeError('mailbox message field "taskContext" must be an object');
1544
- }
1545
- const status = value["status"];
1546
- if (status !== void 0 && (typeof status !== "string" || !TASK_STATUSES.has(status))) {
1547
- throw new TypeError("mailbox message taskContext status is invalid");
1548
- }
1549
- return {
1550
- ...optionalString(value, "agentRole"),
1551
- ...optionalString(value, "agentName"),
1552
- ...optionalString(value, "taskId"),
1553
- ...status === void 0 ? {} : { status }
1554
- };
1555
- }
1556
- function parseSessionAffinity(value) {
1557
- if (value === void 0) return void 0;
1558
- if (!isRecord2(value)) {
1559
- throw new TypeError('mailbox message field "sessionAffinity" must be an object');
1560
- }
1561
- const sessionId = value["sessionId"];
1562
- const reportId = value["reportId"];
1563
- const kind = optionalString(value, "kind");
1564
- if (sessionId !== void 0) {
1565
- if (typeof sessionId !== "string" || sessionId.length === 0) {
1566
- throw new TypeError("mailbox message sessionAffinity.sessionId must be a non-empty string");
1567
- }
1568
- return {
1569
- sessionId,
1570
- ...optionalString(value, "reportId"),
1571
- ...kind
1572
- };
1573
- }
1574
- if (typeof reportId !== "string" || reportId.length === 0) {
1575
- throw new TypeError(
1576
- "mailbox message sessionAffinity.reportId must be a non-empty string when sessionId is absent"
1577
- );
1578
- }
1579
- return {
1580
- reportId,
1581
- ...kind
1582
- };
1583
- }
1584
- function parseMailboxMessage(value) {
1585
- if (!isRecord2(value)) throw new TypeError("mailbox message must be an object");
1586
- const to = value["to"] === void 0 ? "" : requiredString(value, "to");
1587
- const completed = value["completed"];
1588
- if (typeof completed !== "boolean") {
1589
- throw new TypeError('mailbox message field "completed" must be a boolean');
1590
- }
1591
- const taskContext = parseTaskContext(value["taskContext"]);
1592
- const audience = parseAudience(value["audience"]);
1593
- const sessionAffinity = parseSessionAffinity(value["sessionAffinity"]);
1594
- return {
1595
- id: requiredString(value, "id"),
1596
- from: requiredString(value, "from"),
1597
- to,
1598
- type: parseMessageType(value["type"]),
1599
- ...audience === void 0 ? {} : { audience },
1600
- subject: requiredString(value, "subject"),
1601
- body: requiredString(value, "body"),
1602
- priority: parsePriority(value["priority"]),
1603
- readBy: parseReadReceipts(value, to),
1604
- completed,
1605
- timestamp: requiredString(value, "timestamp"),
1606
- ...optionalString(value, "completedBy"),
1607
- ...optionalString(value, "outcome"),
1608
- ...optionalString(value, "completedAt"),
1609
- ...optionalString(value, "deletedAt"),
1610
- ...optionalString(value, "deletedBy"),
1611
- ...optionalString(value, "replyTo"),
1612
- ...optionalString(value, "senderSessionId"),
1613
- ...optionalString(value, "expiresAt"),
1614
- ...taskContext === void 0 ? {} : { taskContext },
1615
- ...sessionAffinity === void 0 ? {} : { sessionAffinity }
1616
- };
1617
- }
1618
- function isAckRecord(value) {
1619
- return typeof value === "object" && value !== null && !Array.isArray(value) && value["__ack"] === true;
1620
- }
1621
- function applyAckToMessage(msg, ack) {
1622
- if (ack.read && !(ack.readerId in msg.readBy)) {
1623
- msg.readBy[ack.readerId] = ack.timestamp;
1624
- }
1625
- if (ack.completed && !msg.completed) {
1626
- msg.completed = true;
1627
- msg.completedBy = ack.completedBy ?? ack.readerId;
1628
- msg.completedAt = ack.timestamp;
1629
- }
1630
- if (ack.outcome !== void 0 && msg.outcome !== ack.outcome) {
1631
- msg.outcome = ack.outcome;
1632
- }
1633
- if (ack.deleted === true) {
1634
- msg.deletedAt = ack.timestamp;
1635
- msg.deletedBy = ack.deletedBy ?? ack.readerId;
1636
- }
1637
- if (ack.deleted === false) {
1638
- delete msg.deletedAt;
1639
- delete msg.deletedBy;
1640
- }
1641
- }
1642
-
1643
- // src/coordination/mailbox-receipt-folding.ts
1644
- function isFanOutRecipient(to) {
1645
- if (to === "*") return true;
1646
- if (to.startsWith("@session:")) return true;
1647
- return !to.includes("@") && !to.includes("#");
1648
- }
1649
- function materializeMessage(msg, msgReceipts) {
1650
- const recipientState = foldRecipientState(msg, msgReceipts);
1651
- const legacyGlobalCompletion = classifyLegacyCompletion(msg, msgReceipts);
1652
- return {
1653
- ...msg,
1654
- recipientState,
1655
- ...legacyGlobalCompletion ? { legacyGlobalCompletion: true } : {}
1656
- };
1657
- }
1658
- function foldRecipientState(msg, v2Receipts) {
1659
- const state = {};
1660
- for (const [actorId, readAt] of Object.entries(msg.readBy)) {
1661
- state[actorId] = { actorId, readAt };
1662
- }
1663
- if (msg.completed && msg.completedBy && !isFanOutRecipient(msg.to)) {
1664
- const existing = state[msg.completedBy] ?? { actorId: msg.completedBy };
1665
- state[msg.completedBy] = {
1666
- ...existing,
1667
- completedAt: msg.completedAt ?? msg.timestamp,
1668
- completedBy: msg.completedBy,
1669
- ...msg.outcome !== void 0 ? { outcome: msg.outcome } : {}
1670
- };
1671
- }
1672
- const sorted = [...v2Receipts].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1673
- for (const receipt of sorted) {
1674
- const actorId = receipt.actorId;
1675
- const existing = state[actorId] ?? { actorId };
1676
- const readAt = existing.readAt ?? (receipt.read === true ? receipt.timestamp : void 0);
1677
- let completedAt = existing.completedAt;
1678
- let completedBy = existing.completedBy;
1679
- if (receipt.completed === true) {
1680
- completedAt = receipt.timestamp;
1681
- completedBy = actorId;
1682
- } else if (receipt.completed === false) {
1683
- completedAt = void 0;
1684
- completedBy = void 0;
1685
- }
1686
- const outcome = receipt.outcome !== void 0 ? receipt.outcome : existing.outcome;
1687
- state[actorId] = { actorId, readAt, completedAt, completedBy, outcome };
1688
- }
1689
- return state;
1690
- }
1691
- function classifyLegacyCompletion(msg, v2Receipts) {
1692
- if (!msg.completed) return false;
1693
- if (v2Receipts.some((r) => r.completed === true)) return false;
1694
- return isFanOutRecipient(msg.to);
1695
- }
1696
-
1697
- // src/coordination/mailbox-retention-state.ts
1698
- function resolveMailboxRetentionState(message, agentStatuses) {
1699
- const projection = message;
1700
- if (projection.legacyGlobalCompletion) {
1701
- return { completed: true, completedAt: message.completedAt ?? message.timestamp };
1702
- }
1703
- const recipientState = projection.recipientState;
1704
- if (recipientState === void 0) {
1705
- return message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
1706
- }
1707
- const relevantRecipients = resolveRelevantRecipients(message, recipientState, agentStatuses);
1708
- if (relevantRecipients.length === 0) return { completed: false };
1709
- const completionTimes = [];
1710
- for (const actorId of relevantRecipients) {
1711
- const completedAt = recipientState[actorId]?.completedAt;
1712
- if (completedAt === void 0) return { completed: false };
1713
- completionTimes.push(completedAt);
1714
- }
1715
- return {
1716
- completed: true,
1717
- // Retention starts only after the last intended recipient completed.
1718
- completedAt: completionTimes.reduce((latest, time) => time > latest ? time : latest)
1719
- };
1720
- }
1721
- function projectMailboxCompletion(message, actorId, agentStatuses) {
1722
- const projection = message;
1723
- let state;
1724
- if (actorId === void 0) {
1725
- state = resolveMailboxRetentionState(message, agentStatuses);
1726
- } else if (projection.legacyGlobalCompletion) {
1727
- state = { completed: true, completedAt: message.completedAt ?? message.timestamp };
1728
- } else if (projection.recipientState !== void 0) {
1729
- const completedAt = projection.recipientState[actorId]?.completedAt;
1730
- state = completedAt === void 0 ? { completed: false } : { completed: true, completedAt };
1731
- } else {
1732
- state = message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
1733
- }
1734
- const result = {
1735
- ...projection,
1736
- completed: state.completed,
1737
- readBy: { ...message.readBy }
1738
- };
1739
- if (state.completedAt === void 0) delete result.completedAt;
1740
- else result.completedAt = state.completedAt;
1741
- return result;
1742
- }
1743
- function resolveRelevantRecipients(message, recipientState, agentStatuses) {
1744
- const statuses = agentStatuses ?? [];
1745
- const receiptActors = Object.keys(recipientState);
1746
- if (message.to === "*") {
1747
- if (statuses.length === 0) return [];
1748
- const registeredRecipients = statuses.filter((status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)).map((status) => status.agentId);
1749
- return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
1750
- }
1751
- if (message.to.startsWith("@session:")) {
1752
- if (statuses.length === 0) return [];
1753
- const sessionId = message.to.slice("@session:".length);
1754
- const registeredRecipients = statuses.filter(
1755
- (status) => status.sessionId === sessionId && isMailboxMessageVisibleTo(message, status.agentId, status.role)
1756
- ).map((status) => status.agentId);
1757
- return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
1758
- }
1759
- if (message.to.includes("@")) return [message.to];
1760
- if (statuses.length === 0) return [];
1761
- const aliasRecipients = statuses.filter(
1762
- (status) => (status.role?.toLowerCase() === message.to.toLowerCase() || mailboxIdentityBase(status.agentId) === message.to.toLowerCase()) && isMailboxMessageVisibleTo(message, status.agentId, status.role)
1763
- ).map((status) => status.agentId);
1764
- return [.../* @__PURE__ */ new Set([...aliasRecipients, ...receiptActors])];
1765
- }
1766
-
1767
- // src/coordination/mailbox-status-mappers.ts
1768
- function mapRegisteredAgentsToStatuses(registry, now, staleMs) {
1769
- return Array.from(registry.values()).map((agent) => ({
1770
- agentId: agent.agentId,
1771
- name: agent.name,
1772
- role: agent.role,
1773
- sessionId: agent.sessionId,
1774
- status: agent.status,
1775
- currentTool: agent.currentTool,
1776
- currentTask: agent.currentTask,
1777
- iterations: agent.iterations,
1778
- toolCalls: agent.toolCalls,
1779
- lastActivityAt: agent.lastSeenAt,
1780
- lastSeenAt: agent.lastSeenAt,
1781
- online: now - new Date(agent.lastSeenAt).getTime() < staleMs,
1782
- pid: agent.pid,
1783
- source: agent.source
1784
- })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
1785
- }
1786
- function mapRegisteredClientsToStatuses(registry, now, staleMs) {
1787
- return Array.from(registry.values()).map((client) => ({
1788
- clientId: client.clientId,
1789
- name: client.name,
1790
- source: client.source,
1791
- sessionId: client.sessionId,
1792
- lastSeenAt: client.lastSeenAt,
1793
- online: now - new Date(client.lastSeenAt).getTime() < staleMs,
1794
- pid: client.pid
1795
- })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
1796
- }
1797
-
1798
- // src/coordination/sqlite-mailbox-compaction.ts
1799
- async function purgeStale(ctx, options) {
1800
- const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
1801
- const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
1802
- const statuses = await ctx.getAgentStatuses();
1803
- const now = Date.now();
1804
- let completedPurged = 0;
1805
- let incompletePurged = 0;
1806
- const ids = [];
1807
- const messages = ctx.readMessages();
1808
- for (const message of messages) {
1809
- const retention = resolveMailboxRetentionState(message, statuses);
1810
- const messageTime = new Date(message.timestamp).getTime();
1811
- const completionTime = new Date(retention.completedAt ?? 0).getTime();
1812
- if (retention.completed && completionTime < now - completedMaxAgeMs) {
1813
- completedPurged++;
1814
- ids.push(message.id);
1815
- } else if (!retention.completed && messageTime < now - incompleteMaxAgeMs) {
1816
- incompletePurged++;
1817
- ids.push(message.id);
1818
- }
1819
- }
1820
- ctx.deleteMessages(ids);
1821
- return {
1822
- completedPurged,
1823
- incompletePurged,
1824
- totalPurged: ids.length,
1825
- remaining: messages.length - ids.length
1826
- };
1827
- }
1828
- async function autoCompact(ctx, options) {
1829
- const readMaxAgeMs = options?.readMaxAgeMs ?? AUTO_COMPACT_READ_MAX_AGE_MS;
1830
- const defaultTtlMs = options?.defaultTtlMs ?? AUTO_COMPACT_DEFAULT_TTL_MS;
1831
- const typeTtlMs = options?.typeTtlMs ?? AUTO_COMPACT_TYPE_TTL_MS;
1832
- const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
1833
- const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
1834
- const statuses = await ctx.getAgentStatuses();
1835
- const online = statuses.filter((status) => status.online);
1836
- const now = Date.now();
1837
- let readByAllRemoved = 0;
1838
- let expiredRemoved = 0;
1839
- let stalePurged = 0;
1840
- const ids = [];
1841
- const messages = ctx.readMessages();
1842
- for (const message of messages) {
1843
- const messageTime = new Date(message.timestamp).getTime();
1844
- const expiry = message.expiresAt !== void 0 ? new Date(message.expiresAt).getTime() : messageTime + (typeTtlMs[message.type] ?? defaultTtlMs);
1845
- if (expiry < now) {
1846
- expiredRemoved++;
1847
- ids.push(message.id);
1848
- continue;
1849
- }
1850
- const retention = resolveMailboxRetentionState(message, statuses);
1851
- const eligible = online.filter(
1852
- (status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)
1853
- );
1854
- if (!retention.completed && eligible.length > 0) {
1855
- const readByAll = eligible.every((status) => status.agentId in message.readBy);
1856
- const latestRead = Math.max(
1857
- ...eligible.map((status) => new Date(message.readBy[status.agentId] ?? 0).getTime())
1858
- );
1859
- if (readByAll && latestRead < now - readMaxAgeMs) {
1860
- readByAllRemoved++;
1861
- ids.push(message.id);
1862
- continue;
1863
- }
1864
- }
1865
- const completionTime = new Date(retention.completedAt ?? 0).getTime();
1866
- if (retention.completed && completionTime < now - completedMaxAgeMs || !retention.completed && messageTime < now - incompleteMaxAgeMs) {
1867
- stalePurged++;
1868
- ids.push(message.id);
1869
- }
1870
- }
1871
- ctx.deleteMessages(ids);
1872
- return {
1873
- readByAllRemoved,
1874
- expiredRemoved,
1875
- stalePurged,
1876
- totalRemoved: ids.length,
1877
- remaining: messages.length - ids.length
1878
- };
1879
- }
1880
-
1881
- // src/coordination/mailbox-credential-throttle.ts
1882
- var CREDENTIAL_VERIFY_MAX_FAILURES = 10;
1883
- var CREDENTIAL_VERIFY_WINDOW_MS = 6e4;
1884
- var CREDENTIAL_VERIFY_COOLDOWN_MS = 6e4;
1885
- var MAX_TRACKED_IDS = 4096;
1886
- var CredentialVerifyThrottle = class {
1887
- constructor(maxFailures = CREDENTIAL_VERIFY_MAX_FAILURES, windowMs = CREDENTIAL_VERIFY_WINDOW_MS, cooldownMs = CREDENTIAL_VERIFY_COOLDOWN_MS) {
1888
- this.maxFailures = maxFailures;
1889
- this.windowMs = windowMs;
1890
- this.cooldownMs = cooldownMs;
1891
- }
1892
- maxFailures;
1893
- windowMs;
1894
- cooldownMs;
1895
- records = /* @__PURE__ */ new Map();
1896
- /** Is this credential id allowed another attempt right now? */
1897
- check(credentialId, now = Date.now()) {
1898
- const record = this.records.get(credentialId);
1899
- if (record === void 0) return { allowed: true, retryAfterMs: 0 };
1900
- if (record.blockedUntil > now) {
1901
- return { allowed: false, retryAfterMs: record.blockedUntil - now };
1902
- }
1903
- return { allowed: true, retryAfterMs: 0 };
1904
- }
1905
- /** Record a failed verification. Starts the cooldown once the limit is hit. */
1906
- recordFailure(credentialId, now = Date.now()) {
1907
- const record = this.records.get(credentialId);
1908
- if (record === void 0 || now - record.windowStartedAt > this.windowMs) {
1909
- this.evictIfFull();
1910
- this.records.set(credentialId, { failures: 1, windowStartedAt: now, blockedUntil: 0 });
1911
- return;
1912
- }
1913
- record.failures += 1;
1914
- if (record.failures >= this.maxFailures) {
1915
- record.blockedUntil = now + this.cooldownMs;
1916
- record.failures = 0;
1917
- record.windowStartedAt = now;
1918
- }
1919
- }
1920
- /**
1921
- * Clear the counter after a successful verification. A legitimate holder who
1922
- * fluffed a few attempts is not left in a cooldown they cannot exit.
1923
- */
1924
- recordSuccess(credentialId) {
1925
- this.records.delete(credentialId);
1926
- }
1927
- /** Test seam. */
1928
- reset() {
1929
- this.records.clear();
1930
- }
1931
- evictIfFull() {
1932
- if (this.records.size < MAX_TRACKED_IDS) return;
1933
- const oldest = this.records.keys().next();
1934
- if (!oldest.done) this.records.delete(oldest.value);
1935
- }
1936
- };
1937
- var credentialVerifyThrottle = new CredentialVerifyThrottle();
1938
-
1939
- // src/coordination/sqlite-mailbox-rows.ts
1940
- var MALFORMED_TIMESTAMP = "last_seen_at NOT GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T*'";
1941
- function withoutAggregateCompletion(message) {
1942
- const stored = {
1943
- ...message,
1944
- completed: message.legacyGlobalCompletion === true
1945
- };
1946
- if (!stored.completed) {
1947
- delete stored.completedBy;
1948
- delete stored.completedAt;
1949
- }
1950
- delete stored.outcome;
1951
- return stored;
1952
- }
1953
- function persistMessage(db, message, legacyGlobalCompletion = false) {
1954
- const stored = { ...message, readBy: { ...message.readBy } };
1955
- delete stored.recipientState;
1956
- delete stored.legacyGlobalCompletion;
1957
- db.prepare(`
1958
- INSERT INTO messages(
1959
- id, from_id, to_id, type, priority, timestamp, completed, completed_at,
1960
- deleted_at, sender_session_id, reply_to, expires_at,
1961
- legacy_global_completion, data
1962
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1963
- ON CONFLICT(id) DO UPDATE SET
1964
- from_id = excluded.from_id,
1965
- to_id = excluded.to_id,
1966
- type = excluded.type,
1967
- priority = excluded.priority,
1968
- timestamp = excluded.timestamp,
1969
- completed = excluded.completed,
1970
- completed_at = excluded.completed_at,
1971
- deleted_at = excluded.deleted_at,
1972
- sender_session_id = excluded.sender_session_id,
1973
- reply_to = excluded.reply_to,
1974
- expires_at = excluded.expires_at,
1975
- legacy_global_completion = excluded.legacy_global_completion,
1976
- data = excluded.data
1977
- `).run(
1978
- message.id,
1979
- message.from,
1980
- message.to,
1981
- message.type,
1982
- message.priority,
1983
- message.timestamp,
1984
- message.completed ? 1 : 0,
1985
- message.completedAt ?? null,
1986
- message.deletedAt ?? null,
1987
- message.senderSessionId ?? null,
1988
- message.replyTo ?? null,
1989
- message.expiresAt ?? null,
1990
- legacyGlobalCompletion ? 1 : 0,
1991
- JSON.stringify(stored)
1992
- );
1993
- }
1994
- function persistReceipt(db, messageId, state) {
1995
- db.prepare(`
1996
- INSERT INTO message_receipts(
1997
- message_id, actor_id, read_at, completed_at, completed_by, outcome
1998
- ) VALUES (?, ?, ?, ?, ?, ?)
1999
- ON CONFLICT(message_id, actor_id) DO UPDATE SET
2000
- read_at = excluded.read_at,
2001
- completed_at = excluded.completed_at,
2002
- completed_by = excluded.completed_by,
2003
- outcome = excluded.outcome
2004
- `).run(
2005
- messageId,
2006
- state.actorId,
2007
- state.readAt ?? null,
2008
- state.completedAt ?? null,
2009
- state.completedBy ?? null,
2010
- state.outcome ?? null
2011
- );
2012
- }
2013
- function materializeMessageRows(db, rows) {
2014
- if (rows.length === 0) return [];
2015
- const receiptRows = [];
2016
- for (const ids of chunk(
2017
- rows.map((row) => row.id),
2018
- 400
2019
- )) {
2020
- const receiptSql = `
2021
- SELECT message_id, actor_id, read_at, completed_at, completed_by, outcome
2022
- FROM message_receipts
2023
- WHERE message_id IN (${ids.map(() => "?").join(", ")})
2024
- `;
2025
- receiptRows.push(...db.prepare(receiptSql).all(...ids));
2026
- }
2027
- const receiptState = /* @__PURE__ */ new Map();
2028
- for (const row of receiptRows) {
2029
- const states = receiptState.get(row.message_id) ?? {};
2030
- states[row.actor_id] = {
2031
- actorId: row.actor_id,
2032
- ...row.read_at !== null ? { readAt: row.read_at } : {},
2033
- ...row.completed_at !== null ? { completedAt: row.completed_at } : {},
2034
- ...row.completed_by !== null ? { completedBy: row.completed_by } : {},
2035
- ...row.outcome !== null ? { outcome: row.outcome } : {}
2036
- };
2037
- receiptState.set(row.message_id, states);
2038
- }
2039
- return rows.map((row) => {
2040
- const base = JSON.parse(row.data);
2041
- const recipientState = receiptState.get(row.id) ?? {};
2042
- const readBy = { ...base.readBy };
2043
- for (const state of Object.values(recipientState)) {
2044
- if (state.readAt !== void 0) readBy[state.actorId] = state.readAt;
2045
- }
2046
- return {
2047
- ...base,
2048
- readBy,
2049
- recipientState,
2050
- ...row.legacy_global_completion === 1 ? { legacyGlobalCompletion: true } : {}
2051
- };
2052
- });
2053
- }
2054
- function deleteMessages(db, ids) {
2055
- for (const chunkIds of chunk(ids, 400)) {
2056
- db.prepare(`DELETE FROM messages WHERE id IN (${chunkIds.map(() => "?").join(", ")})`).run(
2057
- ...chunkIds
2058
- );
2059
- }
2060
- }
2061
- function* chunk(values, size) {
2062
- for (let start = 0; start < values.length; start += size) {
2063
- yield values.slice(start, start + size);
2064
- }
2065
- }
2066
- function persistAgent(db, agent) {
2067
- db.prepare(`
2068
- INSERT INTO agents(
2069
- agent_id, session_id, name, role, status, current_tool, current_task,
2070
- iterations, tool_calls, registered_at, last_seen_at, pid, source
2071
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2072
- ON CONFLICT(agent_id) DO UPDATE SET
2073
- session_id = excluded.session_id,
2074
- name = excluded.name,
2075
- role = excluded.role,
2076
- status = excluded.status,
2077
- current_tool = excluded.current_tool,
2078
- current_task = excluded.current_task,
2079
- iterations = excluded.iterations,
2080
- tool_calls = excluded.tool_calls,
2081
- registered_at = excluded.registered_at,
2082
- last_seen_at = excluded.last_seen_at,
2083
- pid = excluded.pid,
2084
- source = excluded.source
2085
- `).run(
2086
- agent.agentId,
2087
- agent.sessionId,
2088
- agent.name,
2089
- agent.role ?? null,
2090
- agent.status,
2091
- agent.currentTool ?? null,
2092
- agent.currentTask ?? null,
2093
- agent.iterations,
2094
- agent.toolCalls,
2095
- agent.registeredAt,
2096
- agent.lastSeenAt,
2097
- agent.pid,
2098
- agent.source ?? null
2099
- );
2100
- }
2101
- function readAgents(db) {
2102
- const rows = db.prepare("SELECT * FROM agents").all();
2103
- const agents = /* @__PURE__ */ new Map();
2104
- for (const row of rows) {
2105
- const agent = {
2106
- agentId: String(row["agent_id"]),
2107
- sessionId: String(row["session_id"]),
2108
- name: String(row["name"]),
2109
- ...row["role"] !== null ? { role: String(row["role"]) } : {},
2110
- status: row["status"],
2111
- ...row["current_tool"] !== null ? { currentTool: String(row["current_tool"]) } : {},
2112
- ...row["current_task"] !== null ? { currentTask: String(row["current_task"]) } : {},
2113
- iterations: Number(row["iterations"]),
2114
- toolCalls: Number(row["tool_calls"]),
2115
- registeredAt: String(row["registered_at"]),
2116
- lastSeenAt: String(row["last_seen_at"]),
2117
- pid: Number(row["pid"]),
2118
- ...row["source"] !== null ? { source: row["source"] } : {}
2119
- };
2120
- agents.set(agent.agentId, agent);
2121
- }
2122
- return agents;
2123
- }
2124
- function pruneAgents(db, maxAgeMs = AGENT_STALE_MS) {
2125
- const cutoff = new Date(Date.now() - Math.max(0, maxAgeMs)).toISOString();
2126
- const result = db.prepare(`DELETE FROM agents WHERE last_seen_at < ? OR ${MALFORMED_TIMESTAMP}`).run(cutoff);
2127
- return Number(result.changes);
2128
- }
2129
- function persistClient(db, client) {
2130
- db.prepare(`
2131
- INSERT INTO clients(
2132
- client_id, session_id, name, source, registered_at, last_seen_at, pid
2133
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
2134
- ON CONFLICT(client_id) DO UPDATE SET
2135
- session_id = excluded.session_id,
2136
- name = excluded.name,
2137
- source = excluded.source,
2138
- registered_at = excluded.registered_at,
2139
- last_seen_at = excluded.last_seen_at,
2140
- pid = excluded.pid
2141
- `).run(
2142
- client.clientId,
2143
- client.sessionId,
2144
- client.name,
2145
- client.source,
2146
- client.registeredAt,
2147
- client.lastSeenAt,
2148
- client.pid
2149
- );
2150
- }
2151
- function readClients(db) {
2152
- const rows = db.prepare("SELECT * FROM clients").all();
2153
- const clients2 = /* @__PURE__ */ new Map();
2154
- for (const row of rows) {
2155
- const client = {
2156
- clientId: String(row["client_id"]),
2157
- sessionId: String(row["session_id"]),
2158
- name: String(row["name"]),
2159
- source: row["source"],
2160
- registeredAt: String(row["registered_at"]),
2161
- lastSeenAt: String(row["last_seen_at"]),
2162
- pid: Number(row["pid"])
2163
- };
2164
- clients2.set(client.clientId, client);
2165
- }
2166
- return clients2;
2167
- }
2168
- function pruneClients(db) {
2169
- const cutoff = new Date(Date.now() - CLIENT_STALE_MS).toISOString();
2170
- const result = db.prepare(`DELETE FROM clients WHERE last_seen_at < ? OR ${MALFORMED_TIMESTAMP}`).run(cutoff);
2171
- return Number(result.changes);
2172
- }
2173
- function persistCredential(db, credential) {
2174
- db.prepare(`
2175
- INSERT INTO credentials(credential_id, status, principal_id, expires_at, data)
2176
- VALUES (?, ?, ?, ?, ?)
2177
- ON CONFLICT(credential_id) DO UPDATE SET
2178
- status = excluded.status,
2179
- principal_id = excluded.principal_id,
2180
- expires_at = excluded.expires_at,
2181
- data = excluded.data
2182
- `).run(
2183
- credential.credentialId,
2184
- credential.status,
2185
- credential.principalId,
2186
- credential.expiresAt,
2187
- JSON.stringify(credential)
2188
- );
2189
- }
2190
-
2191
- // src/coordination/sqlite-mailbox-credentials.ts
2192
- function credentialGet(db, credentialId) {
2193
- const row = db.prepare("SELECT data FROM credentials WHERE credential_id = ?").get(credentialId);
2194
- return row === void 0 ? null : JSON.parse(row.data);
2195
- }
2196
- function credentialList(db) {
2197
- const rows = db.prepare("SELECT data FROM credentials").all();
2198
- return rows.map((row) => JSON.parse(row.data)).sort((left, right) => {
2199
- if (left.status === "active" && right.status !== "active") return -1;
2200
- if (left.status !== "active" && right.status === "active") return 1;
2201
- return right.issuedAt.localeCompare(left.issuedAt);
2202
- });
2203
- }
2204
- function credentialStatusCounts(db) {
2205
- const rows = db.prepare("SELECT status, COUNT(*) AS count FROM credentials GROUP BY status").all();
2206
- return Object.fromEntries(rows.map((row) => [row.status, row.count]));
2207
- }
2208
- function credentialIssue(db, transaction, options) {
2209
- const now = Date.now();
2210
- const issued = createMailboxCredential(options, now);
2211
- transaction(() => {
2212
- if (options.supersedes !== void 0) {
2213
- const old = credentialGet(db, options.supersedes);
2214
- if (old?.status === "active") {
2215
- old.status = "rotated_out";
2216
- old.statusChangedAt = new Date(now).toISOString();
2217
- old.statusReason = "superseded by rotation";
2218
- old.rotationValidUntil = new Date(now + ROTATION_OVERLAP_MS).toISOString();
2219
- persistCredential(db, old);
2220
- }
2221
- }
2222
- persistCredential(db, issued.credential);
2223
- });
2224
- return issued;
2225
- }
2226
- function credentialVerify(db, credentialId, secret) {
2227
- const decision = credentialVerifyThrottle.check(credentialId);
2228
- if (!decision.allowed) {
2229
- return {
2230
- valid: false,
2231
- reason: `too many failed verification attempts \u2014 retry in ${Math.ceil(decision.retryAfterMs / 1e3)}s`
2232
- };
2233
- }
2234
- const result = verifyMailboxCredential(credentialGet(db, credentialId) ?? void 0, secret);
2235
- if (result.valid) credentialVerifyThrottle.recordSuccess(credentialId);
2236
- else credentialVerifyThrottle.recordFailure(credentialId);
2237
- return result;
2238
- }
2239
- function credentialRevoke(db, credentialId, reason, by) {
2240
- const credential = credentialGet(db, credentialId);
2241
- if (credential === null || credential.status === "revoked") return false;
2242
- credential.status = "revoked";
2243
- credential.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
2244
- credential.statusReason = reason ?? "revoked";
2245
- credential.lastModifiedBy = by;
2246
- persistCredential(db, credential);
2247
- return true;
2248
- }
2249
- function credentialRotate(db, transaction, credentialId, options) {
2250
- const old = credentialGet(db, credentialId);
2251
- if (old === null) return null;
2252
- if (old.status === "revoked") return null;
2253
- return credentialIssue(db, transaction, {
2254
- principalId: old.principalId,
2255
- projectId: old.projectId ?? options?.projectId,
2256
- kind: old.kind,
2257
- capabilities: options?.capabilities ?? old.capabilities,
2258
- ttlMs: options?.ttlMs ?? previousCredentialTtlMs(old),
2259
- notBefore: options?.notBefore,
2260
- supersedes: credentialId,
2261
- issuedBy: options?.issuedBy
2262
- });
2263
- }
2264
- function previousCredentialTtlMs(credential) {
2265
- const issuedAt = new Date(credential.issuedAt).getTime();
2266
- const expiresAt = new Date(credential.expiresAt).getTime();
2267
- const ttlMs = expiresAt - issuedAt;
2268
- return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : MAX_CREDENTIAL_TTL[credential.kind];
2269
- }
2270
-
2271
- // src/coordination/sqlite-mailbox-schema.ts
2272
- import * as fs2 from "node:fs";
2273
- import * as path5 from "node:path";
2274
- import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
2275
-
2276
- // src/utils/sqlite-warning.ts
2277
- var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
2278
- function isSqliteExperimentalWarning(warning, rest) {
2279
- const message = typeof warning === "string" ? warning : warning instanceof Error ? warning.message : "";
2280
- const typeOrOptions = rest[0];
2281
- const warningType = typeof warning === "string" ? typeof typeOrOptions === "string" ? typeOrOptions : typeof typeOrOptions === "object" && typeOrOptions !== null && "type" in typeOrOptions && typeof typeOrOptions.type === "string" ? typeOrOptions.type : "" : warning instanceof Error ? warning.name : "";
2282
- const warningCode = typeof warning === "string" ? typeof typeOrOptions === "object" && typeOrOptions !== null && "code" in typeOrOptions && typeof typeOrOptions.code === "string" ? typeOrOptions.code : typeof rest[1] === "string" ? rest[1] : "" : warning instanceof Error && "code" in warning && typeof warning.code === "string" ? warning.code : "";
2283
- return SQLITE_EXPERIMENTAL_WARNING_RE.test(message) && (warningType === "ExperimentalWarning" || warningCode === "ExperimentalWarning");
2284
- }
2285
- function withSqliteExperimentalWarningSuppressed(run) {
2286
- const originalEmitWarning = process.emitWarning;
2287
- const forwardWarning = originalEmitWarning.bind(process);
2288
- process.emitWarning = ((warning, ...rest) => {
2289
- if (isSqliteExperimentalWarning(warning, rest)) return;
2290
- forwardWarning(warning, ...rest);
2291
- });
2292
- try {
2293
- return run();
2294
- } finally {
2295
- process.emitWarning = originalEmitWarning;
2296
- }
2297
- }
2298
-
2299
- // src/coordination/global-mailbox-paths.ts
2300
- var GLOBAL_MAILBOX_FILE = "_mailbox.jsonl";
2301
- var GLOBAL_MAILBOX_CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
2302
-
2303
- // src/coordination/mailbox-parse-state.ts
2304
- function parseMailboxFile(raw) {
2305
- return createMailboxParseState(raw).projections;
2306
- }
2307
- function createMailboxParseState(raw) {
2308
- const state = {
2309
- messages: [],
2310
- projections: [],
2311
- indexById: /* @__PURE__ */ new Map(),
2312
- receiptsByMessage: /* @__PURE__ */ new Map()
2313
- };
2314
- ingestMailboxChunk(state, raw);
2315
- return state;
2316
- }
2317
- function ingestMailboxChunk(state, chunk2) {
2318
- const firstNewIndex = state.messages.length;
2319
- const ackRecords = [];
2320
- const staleExisting = /* @__PURE__ */ new Set();
2321
- for (const line of chunk2.split(LINE_SEPARATOR)) {
2322
- if (line.trim().length === 0) continue;
2323
- let parsed;
2324
- try {
2325
- parsed = JSON.parse(line);
2326
- } catch {
2327
- continue;
2328
- }
2329
- if (isMailboxReceiptRecordV2(parsed)) {
2330
- const list = state.receiptsByMessage.get(parsed.messageId);
2331
- if (list) list.push(parsed);
2332
- else state.receiptsByMessage.set(parsed.messageId, [parsed]);
2333
- for (const index2 of state.indexById.get(parsed.messageId) ?? []) {
2334
- if (index2 < firstNewIndex) staleExisting.add(index2);
2335
- }
2336
- continue;
2337
- }
2338
- if (isAckRecord(parsed)) {
2339
- ackRecords.push(parsed);
2340
- continue;
2341
- }
2342
- let message;
2343
- try {
2344
- message = parseMailboxMessage(parsed);
2345
- } catch {
2346
- continue;
2347
- }
2348
- const index = state.messages.length;
2349
- state.messages.push(message);
2350
- const indices = state.indexById.get(message.id);
2351
- if (indices) indices.push(index);
2352
- else state.indexById.set(message.id, [index]);
2353
- }
2354
- for (const ack of ackRecords) {
2355
- const indices = state.indexById.get(ack.messageId);
2356
- if (indices === void 0 || indices.length === 0) continue;
2357
- const index = indices[indices.length - 1];
2358
- applyAckToMessage(state.messages[index], ack);
2359
- if (index < firstNewIndex) staleExisting.add(index);
2360
- }
2361
- for (const index of staleExisting) {
2362
- const message = state.messages[index];
2363
- state.projections[index] = materializeMessage(
2364
- message,
2365
- state.receiptsByMessage.get(message.id) ?? []
2366
- );
2367
- }
2368
- for (let index = firstNewIndex; index < state.messages.length; index++) {
2369
- const message = state.messages[index];
2370
- state.projections.push(
2371
- materializeMessage(message, state.receiptsByMessage.get(message.id) ?? [])
2372
- );
2373
- }
2374
- }
2375
-
2376
- // src/coordination/mailbox-registry-codec.ts
2377
- function parseAgentRegistryEntry(value) {
2378
- if (typeof value !== "object" || value === null) return null;
2379
- const v = value;
2380
- if (typeof v.agentId !== "string") return null;
2381
- if (typeof v.sessionId !== "string") return null;
2382
- const statuses = ["idle", "busy", "running", "streaming", "waiting_user", "error"];
2383
- const status = typeof v.status === "string" && statuses.includes(v.status) ? v.status : "idle";
2384
- return {
2385
- ...v,
2386
- agentId: v.agentId,
2387
- sessionId: v.sessionId,
2388
- name: typeof v.name === "string" ? v.name : v.agentId,
2389
- registeredAt: typeof v.registeredAt === "string" ? v.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2390
- lastSeenAt: typeof v.lastSeenAt === "string" ? v.lastSeenAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2391
- iterations: typeof v.iterations === "number" && Number.isFinite(v.iterations) ? v.iterations : 0,
2392
- toolCalls: typeof v.toolCalls === "number" && Number.isFinite(v.toolCalls) ? v.toolCalls : 0,
2393
- pid: typeof v.pid === "number" && Number.isFinite(v.pid) ? v.pid : 0,
2394
- status
2395
- };
2396
- }
2397
- function parseClientRegistryEntry(value) {
2398
- if (typeof value !== "object" || value === null) return null;
2399
- const v = value;
2400
- if (typeof v.clientId !== "string") return null;
2401
- if (typeof v.sessionId !== "string") return null;
2402
- const sources = ["repl", "tui", "webui", "http"];
2403
- const source = typeof v.source === "string" && sources.includes(v.source) ? v.source : "http";
2404
- return {
2405
- ...v,
2406
- clientId: v.clientId,
2407
- sessionId: v.sessionId,
2408
- name: typeof v.name === "string" ? v.name : v.clientId,
2409
- registeredAt: typeof v.registeredAt === "string" ? v.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2410
- lastSeenAt: typeof v.lastSeenAt === "string" ? v.lastSeenAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2411
- pid: typeof v.pid === "number" && Number.isFinite(v.pid) ? v.pid : 0,
2412
- source
2413
- };
2414
- }
2415
-
2416
- // src/coordination/sqlite-mailbox-schema.ts
2417
- var SQLITE_MAILBOX_SCHEMA_VERSION = 2;
2418
- var DatabaseSyncCtor;
2419
- function loadDatabaseSync() {
2420
- if (DatabaseSyncCtor) return DatabaseSyncCtor;
2421
- return withSqliteExperimentalWarningSuppressed(() => {
2422
- DatabaseSyncCtor = loadRuntimeDatabaseSync();
2423
- return DatabaseSyncCtor;
2424
- });
2425
- }
2426
- function initializeSchema(ctx) {
2427
- const { db } = ctx;
2428
- db.exec(`
2429
- CREATE TABLE IF NOT EXISTS mailbox_meta (
2430
- key TEXT PRIMARY KEY,
2431
- value TEXT NOT NULL
2432
- );
2433
- CREATE TABLE IF NOT EXISTS messages (
2434
- id TEXT PRIMARY KEY,
2435
- from_id TEXT NOT NULL,
2436
- to_id TEXT NOT NULL,
2437
- type TEXT NOT NULL,
2438
- priority TEXT NOT NULL,
2439
- timestamp TEXT NOT NULL,
2440
- completed INTEGER NOT NULL DEFAULT 0,
2441
- completed_at TEXT,
2442
- deleted_at TEXT,
2443
- sender_session_id TEXT,
2444
- reply_to TEXT,
2445
- expires_at TEXT,
2446
- legacy_global_completion INTEGER NOT NULL DEFAULT 0,
2447
- data TEXT NOT NULL
2448
- );
2449
- CREATE TABLE IF NOT EXISTS message_receipts (
2450
- message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
2451
- actor_id TEXT NOT NULL,
2452
- read_at TEXT,
2453
- completed_at TEXT,
2454
- completed_by TEXT,
2455
- outcome TEXT,
2456
- PRIMARY KEY (message_id, actor_id)
2457
- );
2458
- CREATE TABLE IF NOT EXISTS agents (
2459
- agent_id TEXT PRIMARY KEY,
2460
- session_id TEXT NOT NULL,
2461
- name TEXT NOT NULL,
2462
- role TEXT,
2463
- status TEXT NOT NULL,
2464
- current_tool TEXT,
2465
- current_task TEXT,
2466
- iterations INTEGER NOT NULL,
2467
- tool_calls INTEGER NOT NULL,
2468
- registered_at TEXT NOT NULL,
2469
- last_seen_at TEXT NOT NULL,
2470
- pid INTEGER NOT NULL,
2471
- source TEXT
2472
- );
2473
- CREATE TABLE IF NOT EXISTS clients (
2474
- client_id TEXT PRIMARY KEY,
2475
- session_id TEXT NOT NULL,
2476
- name TEXT NOT NULL,
2477
- source TEXT NOT NULL,
2478
- registered_at TEXT NOT NULL,
2479
- last_seen_at TEXT NOT NULL,
2480
- pid INTEGER NOT NULL
2481
- );
2482
- CREATE TABLE IF NOT EXISTS credentials (
2483
- credential_id TEXT PRIMARY KEY,
2484
- status TEXT NOT NULL,
2485
- principal_id TEXT NOT NULL,
2486
- expires_at TEXT NOT NULL,
2487
- data TEXT NOT NULL
2488
- );
2489
- CREATE INDEX IF NOT EXISTS idx_messages_to_timestamp ON messages(to_id, timestamp DESC);
2490
- CREATE INDEX IF NOT EXISTS idx_messages_from_timestamp ON messages(from_id, timestamp DESC);
2491
- CREATE INDEX IF NOT EXISTS idx_messages_type_timestamp ON messages(type, timestamp DESC);
2492
- CREATE INDEX IF NOT EXISTS idx_messages_deleted_at ON messages(deleted_at);
2493
- CREATE INDEX IF NOT EXISTS idx_messages_session_timestamp ON messages(sender_session_id, timestamp DESC);
2494
- CREATE INDEX IF NOT EXISTS idx_messages_reply_timestamp ON messages(reply_to, timestamp DESC);
2495
- CREATE INDEX IF NOT EXISTS idx_receipts_actor ON message_receipts(actor_id, message_id);
2496
- CREATE INDEX IF NOT EXISTS idx_agents_last_seen ON agents(last_seen_at);
2497
- CREATE INDEX IF NOT EXISTS idx_clients_last_seen ON clients(last_seen_at);
2498
- CREATE INDEX IF NOT EXISTS idx_credentials_status ON credentials(status, expires_at);
2499
- `);
2500
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO NOTHING").run(
2501
- "schema_version",
2502
- String(SQLITE_MAILBOX_SCHEMA_VERSION)
2503
- );
2504
- const schema = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("schema_version");
2505
- const foundVersion = Number(schema?.value);
2506
- if (!Number.isInteger(foundVersion) || foundVersion < 1 || foundVersion > SQLITE_MAILBOX_SCHEMA_VERSION) {
2507
- throw new Error(
2508
- `Unsupported mailbox SQLite schema ${schema?.value ?? "missing"}; this build supports ${SQLITE_MAILBOX_SCHEMA_VERSION}`
2509
- );
2510
- }
2511
- if (foundVersion < SQLITE_MAILBOX_SCHEMA_VERSION) {
2512
- db.prepare("UPDATE mailbox_meta SET value = ? WHERE key = ?").run(
2513
- String(SQLITE_MAILBOX_SCHEMA_VERSION),
2514
- "schema_version"
2515
- );
2516
- }
2517
- migrateLegacyCredentials(ctx);
2518
- }
2519
- function migrateLegacyCredentials(ctx) {
2520
- const { db } = ctx;
2521
- const marker = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("legacy_credentials_imported");
2522
- if (marker !== void 0) return;
2523
- const legacyPath = path5.join(ctx.projectDir, CREDENTIAL_STORE_FILE);
2524
- const credentials = [];
2525
- try {
2526
- for (const line of fs2.readFileSync(legacyPath, "utf8").split(/\r?\n/u)) {
2527
- if (!line.trim()) continue;
2528
- try {
2529
- const credential = JSON.parse(line);
2530
- if (typeof credential.credentialId === "string" && typeof credential.verifier === "string" && typeof credential.principalId === "string")
2531
- credentials.push(credential);
2532
- } catch {
2533
- }
2534
- }
2535
- } catch (error) {
2536
- const code = error.code;
2537
- if (code !== "ENOENT") throw error;
2538
- }
2539
- ctx.transaction(() => {
2540
- for (const credential of credentials) persistCredential(db, credential);
2541
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?)").run(
2542
- "legacy_credentials_imported",
2543
- (/* @__PURE__ */ new Date()).toISOString()
2544
- );
2545
- });
2546
- }
2547
- function migrateLegacyFiles(ctx) {
2548
- const { db } = ctx;
2549
- const marker = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("legacy_files_imported");
2550
- if (marker !== void 0) return;
2551
- const messages = readLegacyMessages(ctx.projectDir);
2552
- const agents = readLegacyRegistry(
2553
- path5.join(ctx.projectDir, "_mailbox.registry.json"),
2554
- parseAgentRegistryEntry
2555
- );
2556
- const clients2 = readLegacyRegistry(
2557
- path5.join(ctx.projectDir, GLOBAL_MAILBOX_CLIENT_REGISTRY_FILE),
2558
- parseClientRegistryEntry
2559
- );
2560
- ctx.transaction(() => {
2561
- for (const message of messages) {
2562
- const projection = message;
2563
- persistMessage(db, message, projection.legacyGlobalCompletion === true);
2564
- for (const state of Object.values(projection.recipientState ?? {})) {
2565
- persistReceipt(db, message.id, state);
2566
- }
2567
- }
2568
- for (const agent of agents.values()) persistAgent(db, agent);
2569
- for (const client of clients2.values()) persistClient(db, client);
2570
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?)").run(
2571
- "legacy_files_imported",
2572
- (/* @__PURE__ */ new Date()).toISOString()
2573
- );
2574
- });
2575
- }
2576
- function readLegacyMessages(projectDir2) {
2577
- try {
2578
- return parseMailboxFile(fs2.readFileSync(path5.join(projectDir2, GLOBAL_MAILBOX_FILE), "utf8"));
2579
- } catch (error) {
2580
- if (error.code === "ENOENT") return [];
2581
- throw error;
2582
- }
2583
- }
2584
- function readLegacyRegistry(filePath, parseEntry) {
2585
- try {
2586
- const raw = JSON.parse(fs2.readFileSync(filePath, "utf8"));
2587
- const result = /* @__PURE__ */ new Map();
2588
- for (const [id, value] of Object.entries(raw)) {
2589
- const parsed = parseEntry(value);
2590
- if (parsed !== null) result.set(id, parsed);
2591
- }
2592
- return result;
2593
- } catch (error) {
2594
- if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
2595
- throw error;
2596
- }
2597
- }
2598
-
2599
- // src/coordination/sqlite-mailbox.ts
2600
- var SQLITE_MAILBOX_FILE = "_mailbox.sqlite";
2601
- var HEARTBEAT_TRACKING_MAX_ENTRIES = 512;
2602
- var HEARTBEAT_TRACKING_TTL_MS = 30 * 6e4;
2603
- var SqliteMailbox = class {
2604
- constructor(projectDir2, events2, eventEmitter2) {
2605
- this.projectDir = projectDir2;
2606
- fs3.mkdirSync(projectDir2, { recursive: true });
2607
- this.databasePath = path6.join(projectDir2, SQLITE_MAILBOX_FILE);
2608
- this.messagePath = this.databasePath;
2609
- this.events = events2;
2610
- this.eventEmitter = eventEmitter2;
2611
- const Database = loadDatabaseSync();
2612
- this.db = new Database(this.databasePath);
2613
- this.db.exec("PRAGMA journal_mode = WAL");
2614
- this.db.exec("PRAGMA synchronous = NORMAL");
2615
- this.db.exec("PRAGMA foreign_keys = ON");
2616
- this.db.exec("PRAGMA busy_timeout = 5000");
2617
- initializeSchema(this.schemaCtx());
2618
- migrateLegacyFiles(this.schemaCtx());
2619
- }
2620
- projectDir;
2621
- databasePath;
2622
- /** Compatibility alias used by project-server health/status consumers. */
2623
- messagePath;
2624
- eventEmitter;
2625
- db;
2626
- events;
2627
- lastHeartbeat = /* @__PURE__ */ new Map();
2628
- lastClientHeartbeat = /* @__PURE__ */ new Map();
2629
- autoCompactTimer = null;
2630
- autoCompactInFlight;
2631
- closed = false;
2632
- stmt(sql) {
2633
- return this.db.prepare(sql);
2634
- }
2635
- transaction(run) {
2636
- this.db.exec("BEGIN IMMEDIATE");
2637
- try {
2638
- const result = run();
2639
- this.db.exec("COMMIT");
2640
- return result;
2641
- } catch (error) {
2642
- this.db.exec("ROLLBACK");
2643
- throw error;
2644
- }
2645
- }
2646
- /** Bundle of store state the schema/migration module operates on. */
2647
- schemaCtx() {
2648
- return {
2649
- db: this.db,
2650
- projectDir: this.projectDir,
2651
- transaction: (run) => this.transaction(run)
2652
- };
2653
- }
2654
- persistMessage(message, legacyGlobalCompletion = false) {
2655
- persistMessage(this.db, message, legacyGlobalCompletion);
2656
- }
2657
- persistReceipt(messageId, state) {
2658
- persistReceipt(this.db, messageId, state);
2659
- }
2660
- materializeMessageRows(rows) {
2661
- return materializeMessageRows(this.db, rows);
2662
- }
2663
- readMessages() {
2664
- const rows = this.stmt(
2665
- "SELECT id, data, legacy_global_completion FROM messages"
2666
- ).all();
2667
- return this.materializeMessageRows(rows);
2668
- }
2669
- findMessage(messageId) {
2670
- const row = this.stmt(
2671
- "SELECT id, data, legacy_global_completion FROM messages WHERE id = ?"
2672
- ).get(messageId);
2673
- return row === void 0 ? void 0 : this.materializeMessageRows([row])[0];
2674
- }
2675
- async send(input) {
2676
- return this.sendMessage(input, false);
2677
- }
2678
- async sendRuntimeControl(input) {
2679
- return this.sendMessage({ ...input, type: "control" }, true);
2680
- }
2681
- async sendMessage(input, allowRuntimeControl) {
2682
- const type = normalizeMailboxMessageType(input.type);
2683
- const to = normalizeRecipient(input.to, input.senderSessionId);
2684
- if (!(allowRuntimeControl && type === "control")) validateSendType(type, to);
2685
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2686
- const message = {
2687
- id: randomUUID2(),
2688
- from: input.from,
2689
- to,
2690
- type,
2691
- ...input.audience !== void 0 && input.audience !== "all" ? { audience: input.audience } : {},
2692
- subject: input.subject,
2693
- body: input.body,
2694
- priority: input.priority ?? "normal",
2695
- readBy: {},
2696
- completed: false,
2697
- timestamp,
2698
- ...input.replyTo !== void 0 ? { replyTo: input.replyTo } : {},
2699
- ...input.taskContext !== void 0 ? { taskContext: input.taskContext } : {},
2700
- ...input.senderSessionId !== void 0 ? { senderSessionId: input.senderSessionId } : {},
2701
- ...input.sessionAffinity !== void 0 ? { sessionAffinity: input.sessionAffinity } : {},
2702
- ...input.ttlMs !== void 0 ? { expiresAt: new Date(Date.now() + input.ttlMs).toISOString() } : {}
2703
- };
2704
- this.persistMessage(message);
2705
- this.events?.emitCustom("mailbox.message_sent", {
2706
- messageId: message.id,
2707
- from: message.from,
2708
- to: message.to,
2709
- type: message.type,
2710
- subject: message.subject
2711
- });
2712
- this.eventEmitter?.emit({
2713
- type: "message.sent",
2714
- messageId: message.id,
2715
- from: message.from,
2716
- to: message.to,
2717
- audience: message.audience,
2718
- timestamp
2719
- });
2720
- return message;
2721
- }
2722
- async query(query) {
2723
- const type = query.type === void 0 ? void 0 : normalizeMailboxMessageType(query.type);
2724
- const priorityRank = { low: 0, normal: 1, high: 2 };
2725
- const minimumRank = query.minPriority === void 0 ? 0 : priorityRank[query.minPriority];
2726
- if (query.ids !== void 0 && query.ids.length === 0) return [];
2727
- const statuses = query.unreadBy === void 0 ? await this.getAgentStatuses() : void 0;
2728
- const where = [];
2729
- const params = [];
2730
- if (query.ids !== void 0) {
2731
- where.push(`id IN (${query.ids.map(() => "?").join(", ")})`);
2732
- params.push(...query.ids);
2733
- }
2734
- if (query.to !== void 0) {
2735
- where.push("(to_id = ? OR to_id = ?)");
2736
- params.push(query.to, "*");
2737
- }
2738
- if (query.from !== void 0) {
2739
- where.push("from_id = ?");
2740
- params.push(query.from);
2741
- }
2742
- if (query.sessionId !== void 0) {
2743
- where.push("sender_session_id = ?");
2744
- params.push(query.sessionId);
2745
- }
2746
- if (type !== void 0) {
2747
- where.push("type = ?");
2748
- params.push(type);
2749
- }
2750
- if (query.minPriority !== void 0) {
2751
- where.push(`CASE priority WHEN 'high' THEN 2 WHEN 'low' THEN 0 ELSE 1 END >= ?`);
2752
- params.push(minimumRank);
2753
- }
2754
- if (query.since !== void 0) {
2755
- where.push("timestamp > ?");
2756
- params.push(query.since);
2757
- }
2758
- if (!query.includeDeleted) where.push("deleted_at IS NULL");
2759
- if (query.replyTo !== void 0) {
2760
- where.push("reply_to = ?");
2761
- params.push(query.replyTo);
2762
- }
2763
- if (query.unreadBy !== void 0) {
2764
- if (!isMailboxLeader(query.unreadBy, query.readerRole)) {
2765
- where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
2766
- }
2767
- if (!query.incompleteOnly) {
2768
- where.push(`NOT EXISTS (
2769
- SELECT 1 FROM message_receipts AS unread_receipt
2770
- WHERE unread_receipt.message_id = messages.id
2771
- AND unread_receipt.actor_id = ?
2772
- AND unread_receipt.read_at IS NOT NULL
2773
- )`);
2774
- params.push(query.unreadBy);
2775
- where.push(`NOT EXISTS (
2776
- SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
2777
- WHERE legacy_read.key = ?
2778
- )`);
2779
- params.push(query.unreadBy);
2780
- } else {
2781
- where.push("legacy_global_completion = 0");
2782
- where.push(`NOT EXISTS (
2783
- SELECT 1 FROM message_receipts AS completed_receipt
2784
- WHERE completed_receipt.message_id = messages.id
2785
- AND completed_receipt.actor_id = ?
2786
- AND completed_receipt.completed_at IS NOT NULL
2787
- )`);
2788
- params.push(query.unreadBy);
2789
- where.push(`(
2790
- completed = 0 OR EXISTS (
2791
- SELECT 1 FROM message_receipts AS any_receipt
2792
- WHERE any_receipt.message_id = messages.id
2793
- )
2794
- )`);
2795
- }
2796
- }
2797
- const canPreLimit = !query.incompleteOnly || query.unreadBy !== void 0;
2798
- let sql = "SELECT id, data, legacy_global_completion FROM messages";
2799
- if (where.length > 0) sql += ` WHERE ${where.join(" AND ")}`;
2800
- sql += " ORDER BY timestamp DESC, rowid DESC";
2801
- if (canPreLimit) {
2802
- sql += " LIMIT ?";
2803
- params.push(query.limit ?? 50);
2804
- }
2805
- const rows = this.stmt(sql).all(...params);
2806
- const idFilter = query.ids === void 0 ? void 0 : new Set(query.ids);
2807
- const filtered = this.materializeMessageRows(rows).filter((message) => {
2808
- if (idFilter !== void 0 && !idFilter.has(message.id)) return false;
2809
- if (query.to !== void 0 && message.to !== query.to && message.to !== "*") return false;
2810
- if (query.from !== void 0 && message.from !== query.from) return false;
2811
- if (query.sessionId !== void 0 && message.senderSessionId !== query.sessionId)
2812
- return false;
2813
- if (query.unreadBy !== void 0 && !isMailboxMessageVisibleTo(message, query.unreadBy, query.readerRole))
2814
- return false;
2815
- if (!query.incompleteOnly && query.unreadBy !== void 0 && query.unreadBy in message.readBy)
2816
- return false;
2817
- if (query.incompleteOnly && (query.unreadBy === void 0 ? projectMailboxCompletion(message, void 0, statuses).completed : isMessageCompletedForActor(message, query.unreadBy)))
2818
- return false;
2819
- if (type !== void 0 && message.type !== type) return false;
2820
- if (priorityRank[message.priority] < minimumRank) return false;
2821
- if (query.since !== void 0 && message.timestamp <= query.since) return false;
2822
- if (!query.includeDeleted && message.deletedAt !== void 0) return false;
2823
- if (query.replyTo !== void 0 && message.replyTo !== query.replyTo) return false;
2824
- return true;
2825
- });
2826
- let messages = filtered;
2827
- if (query.currentSessionId !== void 0 || query.sessionAffinityCtx !== void 0) {
2828
- const kept = [];
2829
- for (const message of filtered) {
2830
- if (await acceptMailboxMessageForSession(
2831
- message,
2832
- query.currentSessionId,
2833
- query.sessionAffinityCtx
2834
- )) {
2835
- kept.push(message);
2836
- }
2837
- }
2838
- messages = kept;
2839
- }
2840
- messages.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
2841
- return messages.slice(0, query.limit ?? 50).map((message) => {
2842
- const copy = {
2843
- ...projectMailboxCompletion(message, query.unreadBy, statuses),
2844
- readBy: { ...message.readBy }
2845
- };
2846
- if (!query.includeReceiptState) {
2847
- delete copy.recipientState;
2848
- delete copy.legacyGlobalCompletion;
2849
- }
2850
- return copy;
2851
- });
2852
- }
2853
- async ack(input) {
2854
- const results = await this.ackMany({ acks: [input] });
2855
- return results[0] ?? null;
2856
- }
2857
- async ackMany(input) {
2858
- if (input.acks.length === 0) return [];
2859
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2860
- const changed = /* @__PURE__ */ new Set();
2861
- const updated = this.transaction(() => {
2862
- const results = [];
2863
- for (const ack of input.acks) {
2864
- const message = this.findMessage(ack.messageId);
2865
- if (message === void 0) continue;
2866
- const current = message.recipientState[ack.readerId] ?? { actorId: ack.readerId };
2867
- const state = { ...current };
2868
- let didChange = false;
2869
- if (ack.read !== false && state.readAt === void 0) {
2870
- state.readAt = timestamp;
2871
- message.readBy[ack.readerId] = timestamp;
2872
- didChange = true;
2873
- }
2874
- if (ack.completed === true && state.completedAt === void 0 && message.legacyGlobalCompletion !== true) {
2875
- state.completedAt = timestamp;
2876
- state.completedBy = ack.readerId;
2877
- didChange = true;
2878
- }
2879
- if (ack.read === false && ack.completed === false && state.completedAt !== void 0) {
2880
- delete state.completedAt;
2881
- delete state.completedBy;
2882
- didChange = true;
2883
- }
2884
- if (ack.outcome !== void 0 && state.outcome !== ack.outcome) {
2885
- state.outcome = ack.outcome;
2886
- didChange = true;
2887
- }
2888
- message.recipientState = {
2889
- ...message.recipientState,
2890
- [ack.readerId]: state
2891
- };
2892
- const actorCompleted = state.completedAt !== void 0;
2893
- message.completed = message.legacyGlobalCompletion === true || actorCompleted;
2894
- if (actorCompleted) {
2895
- message.completedBy = state.completedBy ?? ack.readerId;
2896
- message.completedAt = state.completedAt;
2897
- } else if (message.legacyGlobalCompletion !== true) {
2898
- delete message.completedBy;
2899
- delete message.completedAt;
2900
- }
2901
- message.outcome = state.outcome;
2902
- if (didChange) {
2903
- this.persistReceipt(message.id, state);
2904
- this.persistMessage(
2905
- isFanOutRecipient(message.to) ? withoutAggregateCompletion(message) : message,
2906
- message.legacyGlobalCompletion === true
2907
- );
2908
- changed.add(message.id);
2909
- }
2910
- results.push({ ...message, readBy: { ...message.readBy } });
2911
- }
2912
- return results;
2913
- });
2914
- for (const message of updated) {
2915
- if (!changed.has(message.id)) continue;
2916
- this.eventEmitter?.emit({
2917
- type: "message.acked",
2918
- messageId: message.id,
2919
- from: message.from,
2920
- to: message.to,
2921
- audience: message.audience,
2922
- timestamp
2923
- });
2924
- }
2925
- return updated;
2926
- }
2927
- /**
2928
- * Count the messages this actor has neither read nor completed.
2929
- *
2930
- * Pushed into SQL rather than filtering `readMessages()`. The pre-tool hook
2931
- * asks for this repeatedly — `mailbox-hooks.ts` throttles it to once a
2932
- * second, which bounds the frequency but not the cost — and the JS form
2933
- * materialized EVERY row in `messages`, joined the whole `message_receipts`
2934
- * table, and folded per-actor receipt state across all of them just to
2935
- * return an integer.
2936
- *
2937
- * The predicate is deliberately the same one {@link query} builds for
2938
- * `{ unreadBy, incompleteOnly }`, because it has to agree exactly with
2939
- * `isMessageCompletedForActor` and `isMailboxMessageVisibleTo`:
2940
- *
2941
- * - **unread** — no per-actor receipt carrying `read_at`, and no legacy
2942
- * `readBy` key. Both are checked: `ackMany` writes the receipt row AND
2943
- * mirrors the timestamp into the message's `readBy` JSON.
2944
- * - **incomplete** — not `legacy_global_completion`, no per-actor receipt
2945
- * carrying `completed_at`, and the aggregate `completed` flag counts only
2946
- * when the message has no receipts at all (once any actor has a receipt,
2947
- * completion is per-actor and the aggregate flag is not authoritative).
2948
- * - **audience** — `leaders` mail is invisible unless the actor's base
2949
- * identity is `leader`. This call path carries no role, matching the
2950
- * `isMailboxMessageVisibleTo(message, forAgentId)` it replaces.
2951
- */
2952
- async unreadCount(forAgentId, sessionId, ctx) {
2953
- const sessionAddress = sessionId === void 0 ? void 0 : sessionRecipient(sessionId);
2954
- const where = [];
2955
- const params = [];
2956
- const recipients = ["to_id = ?", "to_id = '*'"];
2957
- params.push(forAgentId);
2958
- if (sessionAddress !== void 0) {
2959
- recipients.push("to_id = ?");
2960
- params.push(sessionAddress);
2961
- }
2962
- where.push(`(${recipients.join(" OR ")})`);
2963
- where.push("deleted_at IS NULL");
2964
- if (!isMailboxLeader(forAgentId)) {
2965
- where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
2966
- }
2967
- where.push(`NOT EXISTS (
2968
- SELECT 1 FROM message_receipts AS read_receipt
2969
- WHERE read_receipt.message_id = messages.id
2970
- AND read_receipt.actor_id = ?
2971
- AND read_receipt.read_at IS NOT NULL
2972
- )`);
2973
- params.push(forAgentId);
2974
- where.push(`NOT EXISTS (
2975
- SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
2976
- WHERE legacy_read.key = ?
2977
- )`);
2978
- params.push(forAgentId);
2979
- where.push("legacy_global_completion = 0");
2980
- where.push(`NOT EXISTS (
2981
- SELECT 1 FROM message_receipts AS completed_receipt
2982
- WHERE completed_receipt.message_id = messages.id
2983
- AND completed_receipt.actor_id = ?
2984
- AND completed_receipt.completed_at IS NOT NULL
2985
- )`);
2986
- params.push(forAgentId);
2987
- where.push(`(
2988
- completed = 0 OR EXISTS (
2989
- SELECT 1 FROM message_receipts AS any_receipt
2990
- WHERE any_receipt.message_id = messages.id
2991
- )
2992
- )`);
2993
- if (sessionId === void 0 && ctx === void 0) {
2994
- const row = this.stmt(
2995
- `SELECT COUNT(*) AS total FROM messages WHERE ${where.join(" AND ")}`
2996
- ).get(...params);
2997
- return Number(row?.total ?? 0);
2998
- }
2999
- const rows = this.stmt(
3000
- `SELECT id, data, legacy_global_completion FROM messages WHERE ${where.join(" AND ")}`
3001
- ).all(...params);
3002
- let total = 0;
3003
- for (const message of this.materializeMessageRows(rows)) {
3004
- if (await acceptMailboxMessageForSession(message, sessionId, ctx)) total += 1;
3005
- }
3006
- return total;
3007
- }
3008
- async softDelete(mailId, by) {
3009
- const message = this.findMessage(mailId);
3010
- if (message === void 0) return null;
3011
- if (message.deletedAt !== void 0) return { ...message, readBy: { ...message.readBy } };
3012
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3013
- message.deletedAt = timestamp;
3014
- message.deletedBy = by;
3015
- const previousState = message.recipientState[by] ?? { actorId: by };
3016
- const state = {
3017
- ...previousState,
3018
- readAt: previousState.readAt ?? timestamp
3019
- };
3020
- message.readBy[by] = state.readAt;
3021
- message.recipientState = { ...message.recipientState, [by]: state };
3022
- this.transaction(() => {
3023
- this.persistReceipt(message.id, state);
3024
- this.persistMessage(message, message.legacyGlobalCompletion === true);
3025
- });
3026
- this.eventEmitter?.emit({
3027
- type: "message.deleted",
3028
- messageId: message.id,
3029
- from: message.from,
3030
- to: message.to,
3031
- audience: message.audience,
3032
- timestamp
3033
- });
3034
- return { ...message, readBy: { ...message.readBy } };
3035
- }
3036
- async restore(mailId) {
3037
- const message = this.findMessage(mailId);
3038
- if (message === void 0) return null;
3039
- if (message.deletedAt === void 0 && message.deletedBy === void 0) {
3040
- return { ...message, readBy: { ...message.readBy } };
3041
- }
3042
- delete message.deletedAt;
3043
- delete message.deletedBy;
3044
- this.persistMessage(message, message.legacyGlobalCompletion === true);
3045
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3046
- this.eventEmitter?.emit({
3047
- type: "message.restored",
3048
- messageId: message.id,
3049
- from: message.from,
3050
- to: message.to,
3051
- audience: message.audience,
3052
- timestamp
3053
- });
3054
- return { ...message, readBy: { ...message.readBy } };
3055
- }
3056
- persistAgent(agent) {
3057
- persistAgent(this.db, agent);
3058
- }
3059
- readAgents() {
3060
- return readAgents(this.db);
3061
- }
3062
- pruneAgents(maxAgeMs = AGENT_STALE_MS) {
3063
- return pruneAgents(this.db, maxAgeMs);
3064
- }
3065
- async registerAgent(input) {
3066
- this.pruneAgents();
3067
- const now = (/* @__PURE__ */ new Date()).toISOString();
3068
- this.persistAgent({
3069
- agentId: input.agentId,
3070
- sessionId: input.sessionId,
3071
- name: input.name,
3072
- ...input.role !== void 0 ? { role: input.role } : {},
3073
- status: "idle",
3074
- iterations: 0,
3075
- toolCalls: 0,
3076
- registeredAt: now,
3077
- lastSeenAt: now,
3078
- pid: input.pid ?? process.pid,
3079
- ...input.source !== void 0 ? { source: input.source } : {}
3080
- });
3081
- this.events?.emitCustom("mailbox.agent_registered", {
3082
- agentId: input.agentId,
3083
- sessionId: input.sessionId,
3084
- name: input.name,
3085
- role: input.role,
3086
- source: input.source
3087
- });
3088
- }
3089
- /**
3090
- * Throttle bookkeeping only: `agentId`/`clientId` -> last accepted heartbeat.
3091
- *
3092
- * Entries are deleted on a clean deregister, but a crashed or forcibly killed
3093
- * peer never deregisters, so over a long-lived daemon's life these maps grew
3094
- * with every distinct id ever seen. Dropping a stale entry is free: the next
3095
- * heartbeat from that id simply is not throttled and writes once more.
3096
- */
3097
- pruneHeartbeats(map, nowMs) {
3098
- for (const [id, at] of map) {
3099
- if (nowMs - at > HEARTBEAT_TRACKING_TTL_MS) map.delete(id);
3100
- }
3101
- while (map.size > HEARTBEAT_TRACKING_MAX_ENTRIES) {
3102
- const oldest = map.keys().next().value;
3103
- if (oldest === void 0) break;
3104
- map.delete(oldest);
3105
- }
3106
- }
3107
- async deregisterAgent(agentId) {
3108
- this.stmt("DELETE FROM agents WHERE agent_id = ?").run(agentId);
3109
- this.lastHeartbeat.delete(agentId);
3110
- this.events?.emitCustom("mailbox.agent_deregistered", { agentId });
3111
- }
3112
- async heartbeat(input) {
3113
- const nowMs = Date.now();
3114
- if (nowMs - (this.lastHeartbeat.get(input.agentId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
3115
- this.lastHeartbeat.set(input.agentId, nowMs);
3116
- this.pruneHeartbeats(this.lastHeartbeat, nowMs);
3117
- this.pruneAgents();
3118
- const agent = this.readAgents().get(input.agentId);
3119
- if (agent !== void 0) {
3120
- agent.lastSeenAt = new Date(nowMs).toISOString();
3121
- if (input.status !== void 0) agent.status = input.status;
3122
- if (input.currentTool !== void 0) agent.currentTool = input.currentTool;
3123
- if (input.currentTask !== void 0) agent.currentTask = input.currentTask;
3124
- if (input.iterations !== void 0) agent.iterations = input.iterations;
3125
- if (input.toolCalls !== void 0) agent.toolCalls = input.toolCalls;
3126
- this.persistAgent(agent);
3127
- }
3128
- this.events?.emitCustom("mailbox.agent_heartbeat", {
3129
- agentId: input.agentId,
3130
- status: input.status,
3131
- currentTool: input.currentTool,
3132
- currentTask: input.currentTask
3133
- });
3134
- }
3135
- async getAgentStatuses() {
3136
- this.pruneAgents();
3137
- return mapRegisteredAgentsToStatuses(this.readAgents(), Date.now(), AGENT_STALE_MS);
3138
- }
3139
- async purgeAgents(maxAgeMs = AGENT_STALE_MS) {
3140
- return this.pruneAgents(maxAgeMs);
3141
- }
3142
- async getOnlineAgents() {
3143
- return (await this.getAgentStatuses()).filter((agent) => agent.online);
3144
- }
3145
- persistClient(client) {
3146
- persistClient(this.db, client);
3147
- }
3148
- readClients() {
3149
- return readClients(this.db);
3150
- }
3151
- pruneClientsInPlace() {
3152
- return pruneClients(this.db);
3153
- }
3154
- async registerClient(input) {
3155
- this.pruneClientsInPlace();
3156
- const now = (/* @__PURE__ */ new Date()).toISOString();
3157
- this.persistClient({
3158
- clientId: input.clientId,
3159
- sessionId: input.sessionId,
3160
- name: input.name,
3161
- source: input.source,
3162
- registeredAt: now,
3163
- lastSeenAt: now,
3164
- pid: input.pid ?? process.pid
3165
- });
3166
- this.events?.emitCustom("mailbox.client_registered", {
3167
- clientId: input.clientId,
3168
- sessionId: input.sessionId,
3169
- name: input.name,
3170
- source: input.source
3171
- });
3172
- }
3173
- async deregisterClient(clientId) {
3174
- this.stmt("DELETE FROM clients WHERE client_id = ?").run(clientId);
3175
- this.lastClientHeartbeat.delete(clientId);
3176
- this.events?.emitCustom("mailbox.client_deregistered", { clientId });
3177
- }
3178
- async clientHeartbeat(input) {
3179
- const nowMs = Date.now();
3180
- if (nowMs - (this.lastClientHeartbeat.get(input.clientId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
3181
- this.lastClientHeartbeat.set(input.clientId, nowMs);
3182
- this.pruneHeartbeats(this.lastClientHeartbeat, nowMs);
3183
- this.pruneClientsInPlace();
3184
- const client = this.readClients().get(input.clientId);
3185
- if (client !== void 0) {
3186
- client.lastSeenAt = new Date(nowMs).toISOString();
3187
- if (input.sessionId) client.sessionId = input.sessionId;
3188
- this.persistClient(client);
3189
- }
3190
- this.events?.emitCustom("mailbox.client_heartbeat", {
3191
- clientId: input.clientId,
3192
- ...input.sessionId ? { sessionId: input.sessionId } : {}
3193
- });
3194
- }
3195
- async getClientStatuses() {
3196
- this.pruneClientsInPlace();
3197
- return mapRegisteredClientsToStatuses(this.readClients(), Date.now(), CLIENT_STALE_MS);
3198
- }
3199
- async purgeClients() {
3200
- return this.pruneClientsInPlace();
3201
- }
3202
- async clearAll() {
3203
- this.stmt("DELETE FROM messages").run();
3204
- }
3205
- async purgeStale(options) {
3206
- return purgeStale(this.compactionCtx(), options);
3207
- }
3208
- async autoCompact(options) {
3209
- if (this.autoCompactInFlight !== void 0) return this.autoCompactInFlight;
3210
- const inFlight = autoCompact(this.compactionCtx(), options);
3211
- this.autoCompactInFlight = inFlight;
3212
- try {
3213
- return await inFlight;
3214
- } finally {
3215
- if (this.autoCompactInFlight === inFlight) this.autoCompactInFlight = void 0;
3216
- }
3217
- }
3218
- /** Bundle of store operations the retention sweeps drive. */
3219
- compactionCtx() {
3220
- return {
3221
- getAgentStatuses: () => this.getAgentStatuses(),
3222
- readMessages: () => this.readMessages(),
3223
- deleteMessages: (ids) => this.deleteMessages(ids)
3224
- };
3225
- }
3226
- deleteMessages(ids) {
3227
- if (ids.length === 0) return;
3228
- this.transaction(() => deleteMessages(this.db, ids));
3229
- }
3230
- credentialGet(credentialId) {
3231
- return credentialGet(this.db, credentialId);
3232
- }
3233
- credentialList() {
3234
- return credentialList(this.db);
3235
- }
3236
- credentialStatusCounts() {
3237
- return credentialStatusCounts(this.db);
3238
- }
3239
- credentialIssue(options) {
3240
- return credentialIssue(this.db, (run) => this.transaction(run), options);
3241
- }
3242
- credentialVerify(credentialId, secret) {
3243
- return credentialVerify(this.db, credentialId, secret);
3244
- }
3245
- credentialRevoke(credentialId, reason, by) {
3246
- return credentialRevoke(this.db, credentialId, reason, by);
3247
- }
3248
- credentialRotate(credentialId, options) {
3249
- return credentialRotate(this.db, (run) => this.transaction(run), credentialId, options);
3250
- }
3251
- startAutoCompactTimer(options) {
3252
- if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
3253
- const timer = setInterval(() => {
3254
- void this.autoCompact(options).catch(() => {
3255
- });
3256
- }, options?.intervalMs ?? AUTO_COMPACT_INTERVAL_MS);
3257
- timer.unref?.();
3258
- this.autoCompactTimer = timer;
3259
- return () => {
3260
- clearInterval(timer);
3261
- if (this.autoCompactTimer === timer) this.autoCompactTimer = null;
3262
- };
3263
- }
3264
- async close() {
3265
- if (this.closed) return;
3266
- this.closed = true;
3267
- if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
3268
- this.autoCompactTimer = null;
3269
- this.db.close();
3270
- }
3271
- };
3272
-
3273
- // src/coordination/mailbox-project-server.ts
45
+ import { bindProjectEndpoint } from "@wrongstack/persistence";
3274
46
  var DEFAULT_IDLE_MS = 5 * 6e4;
3275
47
  var DEFAULT_CLIENT_LEASE_MS = 45e3;
3276
48
  var MAX_CLIENT_WRITE_BUFFER_BYTES = 8 * 1024 * 1024;
@@ -3280,7 +52,7 @@ function parseArgs(argv) {
3280
52
  if (argv[index] === "--project-dir") projectDir2 = argv[++index];
3281
53
  }
3282
54
  if (!projectDir2) throw new Error("mailbox project server requires --project-dir");
3283
- return { projectDir: path7.resolve(projectDir2) };
55
+ return { projectDir: path.resolve(projectDir2) };
3284
56
  }
3285
57
  useDaemonPerfDefaults();
3286
58
  var { projectDir } = parseArgs(process.argv.slice(2));
@@ -3307,10 +79,10 @@ var stopMemoryWatchdog = startSharedHeapWatchdog({
3307
79
  pendingRequests
3308
80
  })
3309
81
  });
3310
- var authToken = randomBytes2(16).toString("hex");
82
+ var authToken = randomBytes(16).toString("hex");
3311
83
  var markMetadataWritten;
3312
- var metadataWritten = new Promise((resolve5) => {
3313
- markMetadataWritten = resolve5;
84
+ var metadataWritten = new Promise((resolve2) => {
85
+ markMetadataWritten = resolve2;
3314
86
  });
3315
87
  var serverInfo = {
3316
88
  protocolVersion: MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
@@ -3341,7 +113,7 @@ events.onAny((event, payload) => {
3341
113
  });
3342
114
  eventEmitter.subscribe((event) => broadcast({ type: "mailbox-event", event }));
3343
115
  function serverStatus() {
3344
- const databasePath = mailbox?.databasePath ?? path7.join(projectDir, SQLITE_MAILBOX_FILE);
116
+ const databasePath = mailbox?.databasePath ?? path.join(projectDir, SQLITE_MAILBOX_FILE);
3345
117
  return {
3346
118
  ...serverInfo,
3347
119
  clients: clients.size,
@@ -3529,9 +301,9 @@ function handleMessage(state, message) {
3529
301
  scheduleIdleStop();
3530
302
  });
3531
303
  }
3532
- function onData(state, chunk2) {
304
+ function onData(state, chunk) {
3533
305
  state.lastSeenAt = Date.now();
3534
- state.buffer += chunk2;
306
+ state.buffer += chunk;
3535
307
  while (true) {
3536
308
  const newline = state.buffer.indexOf("\n");
3537
309
  if (newline < 0) {
@@ -3595,7 +367,7 @@ async function stop(_reason) {
3595
367
  clearInterval(leaseSweep);
3596
368
  stopAutoCompact?.();
3597
369
  await removeOwnedMetadata();
3598
- const serverClosed = new Promise((resolve5) => server.close(() => resolve5()));
370
+ const serverClosed = new Promise((resolve2) => server.close(() => resolve2()));
3599
371
  for (const state of clients) state.socket.end();
3600
372
  const forceCloseTimer = setTimeout(() => {
3601
373
  for (const state of clients) state.socket.destroy();
@@ -3627,7 +399,7 @@ var server = net.createServer((socket) => {
3627
399
  void metadataWritten.then(() => {
3628
400
  if (!socket.destroyed) send(state, { type: "hello", ...serverInfo });
3629
401
  });
3630
- socket.on("data", (chunk2) => onData(state, chunk2));
402
+ socket.on("data", (chunk) => onData(state, chunk));
3631
403
  socket.on("error", () => {
3632
404
  });
3633
405
  socket.on("close", () => {