@wrongstack/core 0.310.1 → 0.313.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 (187) hide show
  1. package/dist/chronicle/index.js +84 -6391
  2. package/dist/chronicle/project-server.js +84 -4868
  3. package/dist/chunk-2BUUIMQB.js +225 -0
  4. package/dist/chunk-2FK4WTFF.js +207 -0
  5. package/dist/chunk-2FQL4PDC.js +292 -0
  6. package/dist/chunk-2KKODIQU.js +2256 -0
  7. package/dist/chunk-2YU4WVBW.js +134 -0
  8. package/dist/chunk-3E244UDA.js +356 -0
  9. package/dist/chunk-3XPVB7BA.js +135 -0
  10. package/dist/chunk-4LJJYBAU.js +388 -0
  11. package/dist/chunk-4VWO3ZXL.js +342 -0
  12. package/dist/chunk-4XV6IR37.js +187 -0
  13. package/dist/chunk-4ZHTKJHM.js +183 -0
  14. package/dist/chunk-52RTVH4M.js +361 -0
  15. package/dist/chunk-57GEWK5X.js +26 -0
  16. package/dist/chunk-5DSMEVA4.js +931 -0
  17. package/dist/chunk-5RP3QTRL.js +1223 -0
  18. package/dist/chunk-5S437PRC.js +50 -0
  19. package/dist/chunk-5XDUGTKN.js +511 -0
  20. package/dist/chunk-62V2AFVG.js +422 -0
  21. package/dist/chunk-65RDIOR2.js +2470 -0
  22. package/dist/chunk-6AKOTGLM.js +597 -0
  23. package/dist/chunk-7ZHZE5EO.js +28 -0
  24. package/dist/chunk-A455ATZB.js +140 -0
  25. package/dist/chunk-AD7ZFZCS.js +46 -0
  26. package/dist/chunk-AI4LLQ2U.js +856 -0
  27. package/dist/chunk-ASX6AW36.js +789 -0
  28. package/dist/chunk-AT4V4XGI.js +3522 -0
  29. package/dist/chunk-AWNQ6ED6.js +311 -0
  30. package/dist/chunk-B4AMLZPB.js +9 -0
  31. package/dist/chunk-BAKVJLEV.js +417 -0
  32. package/dist/chunk-BJHVULHO.js +55 -0
  33. package/dist/chunk-CB2HFKTP.js +9 -0
  34. package/dist/chunk-CE4ZOCOI.js +1640 -0
  35. package/dist/chunk-CQQL4ZOQ.js +151 -0
  36. package/dist/chunk-DEIQMBD2.js +65 -0
  37. package/dist/chunk-E6I5RXVK.js +58 -0
  38. package/dist/chunk-EFEHDQGY.js +1589 -0
  39. package/dist/chunk-EODDVJBM.js +82 -0
  40. package/dist/chunk-EPC3GZD4.js +308 -0
  41. package/dist/chunk-FAYRX2AD.js +13 -0
  42. package/dist/chunk-FFSX2QDQ.js +362 -0
  43. package/dist/chunk-FHAMQS7Q.js +5856 -0
  44. package/dist/chunk-FIV4IJL7.js +2263 -0
  45. package/dist/chunk-FNXVT7HF.js +52 -0
  46. package/dist/chunk-FSWCWSTS.js +395 -0
  47. package/dist/chunk-FW5LDDMJ.js +16464 -0
  48. package/dist/chunk-FXQLIX3K.js +211 -0
  49. package/dist/chunk-G5AAKEEZ.js +228 -0
  50. package/dist/chunk-G5S3S5ZN.js +38 -0
  51. package/dist/chunk-GAZEWQEZ.js +231 -0
  52. package/dist/chunk-GFJOMSHG.js +125 -0
  53. package/dist/chunk-GFXZTHNI.js +158 -0
  54. package/dist/chunk-GM54GJBU.js +960 -0
  55. package/dist/chunk-GPQ3NJPR.js +900 -0
  56. package/dist/chunk-GTCU6DZ3.js +1 -0
  57. package/dist/chunk-H5E77KUJ.js +15 -0
  58. package/dist/chunk-H72H4XDU.js +1023 -0
  59. package/dist/chunk-HA4ZYA6T.js +178 -0
  60. package/dist/chunk-I45KAW2N.js +5020 -0
  61. package/dist/chunk-IQ35HMQL.js +1396 -0
  62. package/dist/chunk-IYFBFX34.js +86 -0
  63. package/dist/chunk-JJPMWPB6.js +109 -0
  64. package/dist/chunk-JTHKOYWU.js +663 -0
  65. package/dist/chunk-JVGPR3TS.js +365 -0
  66. package/dist/chunk-JVKB2TBL.js +48 -0
  67. package/dist/chunk-JX7VBZDK.js +31 -0
  68. package/dist/chunk-K3G6JBUC.js +5232 -0
  69. package/dist/chunk-K4YUNKU7.js +589 -0
  70. package/dist/chunk-KBZGE6LN.js +710 -0
  71. package/dist/chunk-KDQJCT2J.js +45 -0
  72. package/dist/chunk-KI4L2DUL.js +560 -0
  73. package/dist/chunk-KJ2ZTQSL.js +47 -0
  74. package/dist/chunk-KSJ5UTP7.js +154 -0
  75. package/dist/chunk-LHOA5DWA.js +28 -0
  76. package/dist/chunk-LX5QWLBL.js +27 -0
  77. package/dist/chunk-LZDDLNCZ.js +279 -0
  78. package/dist/chunk-M2WF6AXG.js +115 -0
  79. package/dist/chunk-M7BS2K6U.js +1 -0
  80. package/dist/chunk-NDBIPFNY.js +102 -0
  81. package/dist/chunk-NEMIWEXC.js +1199 -0
  82. package/dist/chunk-NFAYHPRH.js +323 -0
  83. package/dist/chunk-O37BQ65S.js +21 -0
  84. package/dist/chunk-O74MJHXU.js +187 -0
  85. package/dist/chunk-OJZEX73F.js +112 -0
  86. package/dist/chunk-PAAWLLZX.js +74 -0
  87. package/dist/chunk-PFXFQQRT.js +339 -0
  88. package/dist/chunk-PFZO3C6H.js +14 -0
  89. package/dist/chunk-PHH7LD45.js +113 -0
  90. package/dist/chunk-QRTG4XTI.js +188 -0
  91. package/dist/chunk-QVOVMXJR.js +1351 -0
  92. package/dist/chunk-RCQYT2I4.js +757 -0
  93. package/dist/chunk-RD6DEE36.js +1178 -0
  94. package/dist/chunk-RJIZEJ42.js +30 -0
  95. package/dist/chunk-RPOA7IAE.js +150 -0
  96. package/dist/chunk-SN2DQRL6.js +1172 -0
  97. package/dist/chunk-SQJVFEBV.js +41 -0
  98. package/dist/chunk-SQP64RCS.js +5391 -0
  99. package/dist/chunk-SVT4RNTR.js +49 -0
  100. package/dist/chunk-SWUFN7XM.js +43 -0
  101. package/dist/chunk-SXMVYC5H.js +174 -0
  102. package/dist/chunk-SZ6AYUIZ.js +23 -0
  103. package/dist/chunk-T3KGZBU5.js +3171 -0
  104. package/dist/chunk-TBRL2RCC.js +9 -0
  105. package/dist/chunk-TRT2HRWZ.js +18 -0
  106. package/dist/chunk-TYW2GI5I.js +1077 -0
  107. package/dist/chunk-UIWONWVG.js +4959 -0
  108. package/dist/chunk-VB3H5WWB.js +151 -0
  109. package/dist/chunk-W4A6QA3D.js +258 -0
  110. package/dist/chunk-WBPVKEEE.js +227 -0
  111. package/dist/chunk-WEBMSQP2.js +123 -0
  112. package/dist/chunk-WOEZV3Q4.js +333 -0
  113. package/dist/chunk-WQQON3SV.js +473 -0
  114. package/dist/chunk-WQTOVMGV.js +119 -0
  115. package/dist/chunk-WZO23AWO.js +2779 -0
  116. package/dist/chunk-XBDP4QPW.js +7 -0
  117. package/dist/chunk-XEBHSJLT.js +103 -0
  118. package/dist/chunk-XQ2ECNK3.js +1 -0
  119. package/dist/chunk-XQ2XGIBF.js +2086 -0
  120. package/dist/chunk-Y2GE5TSI.js +355 -0
  121. package/dist/chunk-YBUJCZ57.js +351 -0
  122. package/dist/chunk-YNCVSFGF.js +149 -0
  123. package/dist/chunk-YUPMXOGP.js +282 -0
  124. package/dist/chunk-YY6APYUM.js +49 -0
  125. package/dist/chunk-Z2IJUO45.js +691 -0
  126. package/dist/chunk-ZEHUCJHY.js +129 -0
  127. package/dist/chunk-ZYU2YOJ6.js +1496 -0
  128. package/dist/chunk-ZZ4V3WIZ.js +163 -0
  129. package/dist/coordination/agents/index.js +145 -6260
  130. package/dist/coordination/index.js +365 -34323
  131. package/dist/coordination/mailbox-project-server.js +48 -3220
  132. package/dist/coordination/mailbox-types.d.ts +13 -1
  133. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  134. package/dist/coordination/sqlite-mailbox.d.ts +2 -1
  135. package/dist/core/agent.d.ts +14 -1
  136. package/dist/core/index.js +267 -12479
  137. package/dist/core/model-ref.js +5 -23
  138. package/dist/core/provider-runner.d.ts +36 -1
  139. package/dist/design/index.js +50 -1087
  140. package/dist/execution/index.d.ts +1 -1
  141. package/dist/execution/index.js +1599 -21887
  142. package/dist/execution/prompt-enhancer.js +16 -535
  143. package/dist/execution/retry-policy.d.ts +31 -1
  144. package/dist/extension/index.js +7 -311
  145. package/dist/goal/index.js +33 -3435
  146. package/dist/hooks/index.js +14 -1138
  147. package/dist/hq/index.js +112 -5139
  148. package/dist/hq/protocol.js +21 -668
  149. package/dist/index.js +1410 -92655
  150. package/dist/infrastructure/index.js +64 -4811
  151. package/dist/kernel/events/provider-events.d.ts +15 -0
  152. package/dist/kernel/events/wrongtrace-events.d.ts +26 -0
  153. package/dist/kernel/events.d.ts +16 -7
  154. package/dist/kernel/index.js +14 -992
  155. package/dist/models/index.js +40 -1117
  156. package/dist/notifications/index.js +3 -108
  157. package/dist/observability/index.js +17 -279
  158. package/dist/plugin/index.js +584 -14835
  159. package/dist/prompts/index.js +14 -948
  160. package/dist/registry/index.js +9 -822
  161. package/dist/registry-R23UBE7H.js +14 -0
  162. package/dist/replay/index.js +6 -84
  163. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  164. package/dist/review-report-store-OWY3EC3N.js +19 -0
  165. package/dist/security/index.js +114 -2202
  166. package/dist/session-catalog/index.js +36 -2103
  167. package/dist/session-catalog/project-server.js +42 -1435
  168. package/dist/skills/index.js +35 -1578
  169. package/dist/storage/index.js +182 -15600
  170. package/dist/tasking/index.js +10 -676
  171. package/dist/tools/index.js +52 -9589
  172. package/dist/types/config/autonomy.d.ts +5 -0
  173. package/dist/types/config/tools.d.ts +2 -2
  174. package/dist/types/config/ui.d.ts +1 -1
  175. package/dist/types/errors.d.ts +18 -0
  176. package/dist/types/index.js +121 -1341
  177. package/dist/types/provider-runner.d.ts +8 -0
  178. package/dist/types/session-markers.js +8 -144
  179. package/dist/utils/error.js +3 -4
  180. package/dist/utils/expect-defined.js +3 -9
  181. package/dist/utils/heap-watchdog.js +6 -706
  182. package/dist/utils/index.js +269 -5685
  183. package/dist/utils/sage-output-block.js +5 -42
  184. package/dist/wiring/proxy-rewrite.d.ts +92 -4
  185. package/dist/wiring/proxy-rewrite.js +20 -61
  186. package/dist/worktree/index.js +6 -887
  187. package/package.json +4 -4
@@ -1,3220 +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) {
71
- return this.onPattern("*", fn);
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) {
85
- if (this.wildcards.length >= MAX_WILDCARDS) {
86
- this.logger?.error(
87
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). 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 };
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) {
112
- if (this.wildcards.length >= MAX_WILDCARDS) {
113
- this.logger?.error(
114
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
115
- );
116
- return () => {
117
- };
118
- }
119
- const entry = { match: (e) => regex.test(e), fn };
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
+ EventBus
7
+ } from "../chunk-4LJJYBAU.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
+ MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS,
10
+ MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
11
+ SQLITE_MAILBOX_FILE,
12
+ SqliteMailbox,
13
+ encodeMailboxProjectServerMessage,
14
+ isMailboxProjectServerClientMessage,
15
+ mailboxProjectServerEndpoint,
16
+ mailboxProjectServerMetadataPath
17
+ } from "../chunk-5RP3QTRL.js";
18
+ import {
19
+ redactMailboxCredential
20
+ } from "../chunk-GM54GJBU.js";
21
+ import "../chunk-W4A6QA3D.js";
22
+ import {
23
+ restrictFilePermissions
24
+ } from "../chunk-H5E77KUJ.js";
25
+ import {
26
+ useDaemonPerfDefaults
27
+ } from "../chunk-FNXVT7HF.js";
28
+ import {
29
+ startSharedHeapWatchdog
30
+ } from "../chunk-KBZGE6LN.js";
31
+ import "../chunk-LX5QWLBL.js";
32
+ import "../chunk-FAYRX2AD.js";
33
+ import {
34
+ atomicWrite
35
+ } from "../chunk-7ZHZE5EO.js";
36
+ import "../chunk-YNCVSFGF.js";
37
+ import "../chunk-WOEZV3Q4.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-message-codec.ts
1420
- var MESSAGE_TYPES = /* @__PURE__ */ new Set([
1421
- "note",
1422
- "ask",
1423
- "assign",
1424
- "steer",
1425
- "btw",
1426
- "broadcast",
1427
- "status",
1428
- "result",
1429
- "review",
1430
- "control"
1431
- ]);
1432
- var PRIORITIES = /* @__PURE__ */ new Set(["low", "normal", "high"]);
1433
- var AUDIENCES = /* @__PURE__ */ new Set(["all", "leaders"]);
1434
- var TASK_STATUSES = /* @__PURE__ */ new Set([
1435
- "pending",
1436
- "in_progress",
1437
- "completed",
1438
- "failed",
1439
- "idle",
1440
- "running",
1441
- "streaming",
1442
- "waiting_user",
1443
- "error",
1444
- "offline",
1445
- "busy"
1446
- ]);
1447
- function isRecord2(value) {
1448
- return typeof value === "object" && value !== null && !Array.isArray(value);
1449
- }
1450
- function requiredString(record, key) {
1451
- const value = record[key];
1452
- if (typeof value !== "string") {
1453
- throw new TypeError(`mailbox message field "${key}" must be a string`);
1454
- }
1455
- return value;
1456
- }
1457
- function optionalString(record, key) {
1458
- const value = record[key];
1459
- if (value === void 0) return {};
1460
- if (typeof value !== "string") {
1461
- throw new TypeError(`mailbox message field "${key}" must be a string when present`);
1462
- }
1463
- return { [key]: value };
1464
- }
1465
- function parseMessageType(value) {
1466
- if (value === "info") return "note";
1467
- if (value === "task") return "assign";
1468
- if (typeof value === "string" && MESSAGE_TYPES.has(value)) {
1469
- return value;
1470
- }
1471
- throw new TypeError('mailbox message field "type" is invalid');
1472
- }
1473
- function normalizeMailboxMessageType(value) {
1474
- return parseMessageType(value);
1475
- }
1476
- function parsePriority(value) {
1477
- if (typeof value === "string" && PRIORITIES.has(value)) {
1478
- return value;
1479
- }
1480
- if (typeof value === "string") return "normal";
1481
- throw new TypeError('mailbox message field "priority" must be a string');
1482
- }
1483
- function parseAudience(value) {
1484
- if (value === void 0 || value === "all") return void 0;
1485
- if (typeof value === "string" && AUDIENCES.has(value)) {
1486
- return value;
1487
- }
1488
- throw new TypeError('mailbox message field "audience" is invalid');
1489
- }
1490
- function parseReadReceipts(record, to) {
1491
- const value = record["readBy"];
1492
- if (value === void 0) {
1493
- const legacyReadAt = record["readAt"];
1494
- return record["read"] === true && typeof legacyReadAt === "string" ? { [to || "unknown"]: legacyReadAt } : {};
1495
- }
1496
- if (!isRecord2(value)) {
1497
- throw new TypeError('mailbox message field "readBy" must be an object');
1498
- }
1499
- const receipts = {};
1500
- for (const [agentId, timestamp] of Object.entries(value)) {
1501
- if (typeof timestamp !== "string") {
1502
- throw new TypeError("mailbox message read receipt timestamps must be strings");
1503
- }
1504
- receipts[agentId] = timestamp;
1505
- }
1506
- return receipts;
1507
- }
1508
- function parseTaskContext(value) {
1509
- if (value === void 0) return void 0;
1510
- if (!isRecord2(value)) {
1511
- throw new TypeError('mailbox message field "taskContext" must be an object');
1512
- }
1513
- const status = value["status"];
1514
- if (status !== void 0 && (typeof status !== "string" || !TASK_STATUSES.has(status))) {
1515
- throw new TypeError("mailbox message taskContext status is invalid");
1516
- }
1517
- return {
1518
- ...optionalString(value, "agentRole"),
1519
- ...optionalString(value, "agentName"),
1520
- ...optionalString(value, "taskId"),
1521
- ...status === void 0 ? {} : { status }
1522
- };
1523
- }
1524
- function parseSessionAffinity(value) {
1525
- if (value === void 0) return void 0;
1526
- if (!isRecord2(value)) {
1527
- throw new TypeError('mailbox message field "sessionAffinity" must be an object');
1528
- }
1529
- const sessionId = value["sessionId"];
1530
- const reportId = value["reportId"];
1531
- const kind = optionalString(value, "kind");
1532
- if (sessionId !== void 0) {
1533
- if (typeof sessionId !== "string" || sessionId.length === 0) {
1534
- throw new TypeError("mailbox message sessionAffinity.sessionId must be a non-empty string");
1535
- }
1536
- return {
1537
- sessionId,
1538
- ...optionalString(value, "reportId"),
1539
- ...kind
1540
- };
1541
- }
1542
- if (typeof reportId !== "string" || reportId.length === 0) {
1543
- throw new TypeError(
1544
- "mailbox message sessionAffinity.reportId must be a non-empty string when sessionId is absent"
1545
- );
1546
- }
1547
- return {
1548
- reportId,
1549
- ...kind
1550
- };
1551
- }
1552
- function parseMailboxMessage(value) {
1553
- if (!isRecord2(value)) throw new TypeError("mailbox message must be an object");
1554
- const to = value["to"] === void 0 ? "" : requiredString(value, "to");
1555
- const completed = value["completed"];
1556
- if (typeof completed !== "boolean") {
1557
- throw new TypeError('mailbox message field "completed" must be a boolean');
1558
- }
1559
- const taskContext = parseTaskContext(value["taskContext"]);
1560
- const audience = parseAudience(value["audience"]);
1561
- const sessionAffinity = parseSessionAffinity(value["sessionAffinity"]);
1562
- return {
1563
- id: requiredString(value, "id"),
1564
- from: requiredString(value, "from"),
1565
- to,
1566
- type: parseMessageType(value["type"]),
1567
- ...audience === void 0 ? {} : { audience },
1568
- subject: requiredString(value, "subject"),
1569
- body: requiredString(value, "body"),
1570
- priority: parsePriority(value["priority"]),
1571
- readBy: parseReadReceipts(value, to),
1572
- completed,
1573
- timestamp: requiredString(value, "timestamp"),
1574
- ...optionalString(value, "completedBy"),
1575
- ...optionalString(value, "outcome"),
1576
- ...optionalString(value, "completedAt"),
1577
- ...optionalString(value, "deletedAt"),
1578
- ...optionalString(value, "deletedBy"),
1579
- ...optionalString(value, "replyTo"),
1580
- ...optionalString(value, "senderSessionId"),
1581
- ...optionalString(value, "expiresAt"),
1582
- ...taskContext === void 0 ? {} : { taskContext },
1583
- ...sessionAffinity === void 0 ? {} : { sessionAffinity }
1584
- };
1585
- }
1586
- function isAckRecord(value) {
1587
- return typeof value === "object" && value !== null && !Array.isArray(value) && value["__ack"] === true;
1588
- }
1589
- function applyAckToMessage(msg, ack) {
1590
- if (ack.read && !(ack.readerId in msg.readBy)) {
1591
- msg.readBy[ack.readerId] = ack.timestamp;
1592
- }
1593
- if (ack.completed && !msg.completed) {
1594
- msg.completed = true;
1595
- msg.completedBy = ack.completedBy ?? ack.readerId;
1596
- msg.completedAt = ack.timestamp;
1597
- }
1598
- if (ack.outcome !== void 0 && msg.outcome !== ack.outcome) {
1599
- msg.outcome = ack.outcome;
1600
- }
1601
- if (ack.deleted === true) {
1602
- msg.deletedAt = ack.timestamp;
1603
- msg.deletedBy = ack.deletedBy ?? ack.readerId;
1604
- }
1605
- if (ack.deleted === false) {
1606
- delete msg.deletedAt;
1607
- delete msg.deletedBy;
1608
- }
1609
- }
1610
-
1611
- // src/coordination/mailbox-receipt-folding.ts
1612
- function isFanOutRecipient(to) {
1613
- if (to === "*") return true;
1614
- if (to.startsWith("@session:")) return true;
1615
- return !to.includes("@") && !to.includes("#");
1616
- }
1617
- function materializeMessage(msg, msgReceipts) {
1618
- const recipientState = foldRecipientState(msg, msgReceipts);
1619
- const legacyGlobalCompletion = classifyLegacyCompletion(msg, msgReceipts);
1620
- return {
1621
- ...msg,
1622
- recipientState,
1623
- ...legacyGlobalCompletion ? { legacyGlobalCompletion: true } : {}
1624
- };
1625
- }
1626
- function foldRecipientState(msg, v2Receipts) {
1627
- const state = {};
1628
- for (const [actorId, readAt] of Object.entries(msg.readBy)) {
1629
- state[actorId] = { actorId, readAt };
1630
- }
1631
- if (msg.completed && msg.completedBy && !isFanOutRecipient(msg.to)) {
1632
- const existing = state[msg.completedBy] ?? { actorId: msg.completedBy };
1633
- state[msg.completedBy] = {
1634
- ...existing,
1635
- completedAt: msg.completedAt ?? msg.timestamp,
1636
- completedBy: msg.completedBy,
1637
- ...msg.outcome !== void 0 ? { outcome: msg.outcome } : {}
1638
- };
1639
- }
1640
- const sorted = [...v2Receipts].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1641
- for (const receipt of sorted) {
1642
- const actorId = receipt.actorId;
1643
- const existing = state[actorId] ?? { actorId };
1644
- const readAt = existing.readAt ?? (receipt.read === true ? receipt.timestamp : void 0);
1645
- let completedAt = existing.completedAt;
1646
- let completedBy = existing.completedBy;
1647
- if (receipt.completed === true) {
1648
- completedAt = receipt.timestamp;
1649
- completedBy = actorId;
1650
- } else if (receipt.completed === false) {
1651
- completedAt = void 0;
1652
- completedBy = void 0;
1653
- }
1654
- const outcome = receipt.outcome !== void 0 ? receipt.outcome : existing.outcome;
1655
- state[actorId] = { actorId, readAt, completedAt, completedBy, outcome };
1656
- }
1657
- return state;
1658
- }
1659
- function classifyLegacyCompletion(msg, v2Receipts) {
1660
- if (!msg.completed) return false;
1661
- if (v2Receipts.some((r) => r.completed === true)) return false;
1662
- return isFanOutRecipient(msg.to);
1663
- }
1664
-
1665
- // src/coordination/mailbox-retention-state.ts
1666
- function resolveMailboxRetentionState(message, agentStatuses) {
1667
- const projection = message;
1668
- if (projection.legacyGlobalCompletion) {
1669
- return { completed: true, completedAt: message.completedAt ?? message.timestamp };
1670
- }
1671
- const recipientState = projection.recipientState;
1672
- if (recipientState === void 0) {
1673
- return message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
1674
- }
1675
- const relevantRecipients = resolveRelevantRecipients(message, recipientState, agentStatuses);
1676
- if (relevantRecipients.length === 0) return { completed: false };
1677
- const completionTimes = [];
1678
- for (const actorId of relevantRecipients) {
1679
- const completedAt = recipientState[actorId]?.completedAt;
1680
- if (completedAt === void 0) return { completed: false };
1681
- completionTimes.push(completedAt);
1682
- }
1683
- return {
1684
- completed: true,
1685
- // Retention starts only after the last intended recipient completed.
1686
- completedAt: completionTimes.reduce((latest, time) => time > latest ? time : latest)
1687
- };
1688
- }
1689
- function projectMailboxCompletion(message, actorId, agentStatuses) {
1690
- const projection = message;
1691
- let state;
1692
- if (actorId === void 0) {
1693
- state = resolveMailboxRetentionState(message, agentStatuses);
1694
- } else if (projection.legacyGlobalCompletion) {
1695
- state = { completed: true, completedAt: message.completedAt ?? message.timestamp };
1696
- } else if (projection.recipientState !== void 0) {
1697
- const completedAt = projection.recipientState[actorId]?.completedAt;
1698
- state = completedAt === void 0 ? { completed: false } : { completed: true, completedAt };
1699
- } else {
1700
- state = message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
1701
- }
1702
- const result = {
1703
- ...projection,
1704
- completed: state.completed,
1705
- readBy: { ...message.readBy }
1706
- };
1707
- if (state.completedAt === void 0) delete result.completedAt;
1708
- else result.completedAt = state.completedAt;
1709
- return result;
1710
- }
1711
- function resolveRelevantRecipients(message, recipientState, agentStatuses) {
1712
- const statuses = agentStatuses ?? [];
1713
- const receiptActors = Object.keys(recipientState);
1714
- if (message.to === "*") {
1715
- if (statuses.length === 0) return [];
1716
- const registeredRecipients = statuses.filter((status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)).map((status) => status.agentId);
1717
- return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
1718
- }
1719
- if (message.to.startsWith("@session:")) {
1720
- if (statuses.length === 0) return [];
1721
- const sessionId = message.to.slice("@session:".length);
1722
- const registeredRecipients = statuses.filter(
1723
- (status) => status.sessionId === sessionId && isMailboxMessageVisibleTo(message, status.agentId, status.role)
1724
- ).map((status) => status.agentId);
1725
- return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
1726
- }
1727
- if (message.to.includes("@")) return [message.to];
1728
- if (statuses.length === 0) return [];
1729
- const aliasRecipients = statuses.filter(
1730
- (status) => (status.role?.toLowerCase() === message.to.toLowerCase() || mailboxIdentityBase(status.agentId) === message.to.toLowerCase()) && isMailboxMessageVisibleTo(message, status.agentId, status.role)
1731
- ).map((status) => status.agentId);
1732
- return [.../* @__PURE__ */ new Set([...aliasRecipients, ...receiptActors])];
1733
- }
1734
-
1735
- // src/coordination/mailbox-status-mappers.ts
1736
- function mapRegisteredAgentsToStatuses(registry, now, staleMs) {
1737
- return Array.from(registry.values()).map((agent) => ({
1738
- agentId: agent.agentId,
1739
- name: agent.name,
1740
- role: agent.role,
1741
- sessionId: agent.sessionId,
1742
- status: agent.status,
1743
- currentTool: agent.currentTool,
1744
- currentTask: agent.currentTask,
1745
- iterations: agent.iterations,
1746
- toolCalls: agent.toolCalls,
1747
- lastActivityAt: agent.lastSeenAt,
1748
- lastSeenAt: agent.lastSeenAt,
1749
- online: now - new Date(agent.lastSeenAt).getTime() < staleMs,
1750
- pid: agent.pid,
1751
- source: agent.source
1752
- })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
1753
- }
1754
- function mapRegisteredClientsToStatuses(registry, now, staleMs) {
1755
- return Array.from(registry.values()).map((client) => ({
1756
- clientId: client.clientId,
1757
- name: client.name,
1758
- source: client.source,
1759
- sessionId: client.sessionId,
1760
- lastSeenAt: client.lastSeenAt,
1761
- online: now - new Date(client.lastSeenAt).getTime() < staleMs,
1762
- pid: client.pid
1763
- })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
1764
- }
1765
-
1766
- // src/coordination/sqlite-mailbox-compaction.ts
1767
- async function purgeStale(ctx, options) {
1768
- const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
1769
- const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
1770
- const statuses = await ctx.getAgentStatuses();
1771
- const now = Date.now();
1772
- let completedPurged = 0;
1773
- let incompletePurged = 0;
1774
- const ids = [];
1775
- const messages = ctx.readMessages();
1776
- for (const message of messages) {
1777
- const retention = resolveMailboxRetentionState(message, statuses);
1778
- const messageTime = new Date(message.timestamp).getTime();
1779
- const completionTime = new Date(retention.completedAt ?? 0).getTime();
1780
- if (retention.completed && completionTime < now - completedMaxAgeMs) {
1781
- completedPurged++;
1782
- ids.push(message.id);
1783
- } else if (!retention.completed && messageTime < now - incompleteMaxAgeMs) {
1784
- incompletePurged++;
1785
- ids.push(message.id);
1786
- }
1787
- }
1788
- ctx.deleteMessages(ids);
1789
- return {
1790
- completedPurged,
1791
- incompletePurged,
1792
- totalPurged: ids.length,
1793
- remaining: messages.length - ids.length
1794
- };
1795
- }
1796
- async function autoCompact(ctx, options) {
1797
- const readMaxAgeMs = options?.readMaxAgeMs ?? AUTO_COMPACT_READ_MAX_AGE_MS;
1798
- const defaultTtlMs = options?.defaultTtlMs ?? AUTO_COMPACT_DEFAULT_TTL_MS;
1799
- const typeTtlMs = options?.typeTtlMs ?? AUTO_COMPACT_TYPE_TTL_MS;
1800
- const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
1801
- const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
1802
- const statuses = await ctx.getAgentStatuses();
1803
- const online = statuses.filter((status) => status.online);
1804
- const now = Date.now();
1805
- let readByAllRemoved = 0;
1806
- let expiredRemoved = 0;
1807
- let stalePurged = 0;
1808
- const ids = [];
1809
- const messages = ctx.readMessages();
1810
- for (const message of messages) {
1811
- const messageTime = new Date(message.timestamp).getTime();
1812
- const expiry = message.expiresAt !== void 0 ? new Date(message.expiresAt).getTime() : messageTime + (typeTtlMs[message.type] ?? defaultTtlMs);
1813
- if (expiry < now) {
1814
- expiredRemoved++;
1815
- ids.push(message.id);
1816
- continue;
1817
- }
1818
- const retention = resolveMailboxRetentionState(message, statuses);
1819
- const eligible = online.filter(
1820
- (status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)
1821
- );
1822
- if (!retention.completed && eligible.length > 0) {
1823
- const readByAll = eligible.every((status) => status.agentId in message.readBy);
1824
- const latestRead = Math.max(
1825
- ...eligible.map((status) => new Date(message.readBy[status.agentId] ?? 0).getTime())
1826
- );
1827
- if (readByAll && latestRead < now - readMaxAgeMs) {
1828
- readByAllRemoved++;
1829
- ids.push(message.id);
1830
- continue;
1831
- }
1832
- }
1833
- const completionTime = new Date(retention.completedAt ?? 0).getTime();
1834
- if (retention.completed && completionTime < now - completedMaxAgeMs || !retention.completed && messageTime < now - incompleteMaxAgeMs) {
1835
- stalePurged++;
1836
- ids.push(message.id);
1837
- }
1838
- }
1839
- ctx.deleteMessages(ids);
1840
- return {
1841
- readByAllRemoved,
1842
- expiredRemoved,
1843
- stalePurged,
1844
- totalRemoved: ids.length,
1845
- remaining: messages.length - ids.length
1846
- };
1847
- }
1848
-
1849
- // src/coordination/mailbox-credential-throttle.ts
1850
- var CREDENTIAL_VERIFY_MAX_FAILURES = 10;
1851
- var CREDENTIAL_VERIFY_WINDOW_MS = 6e4;
1852
- var CREDENTIAL_VERIFY_COOLDOWN_MS = 6e4;
1853
- var MAX_TRACKED_IDS = 4096;
1854
- var CredentialVerifyThrottle = class {
1855
- constructor(maxFailures = CREDENTIAL_VERIFY_MAX_FAILURES, windowMs = CREDENTIAL_VERIFY_WINDOW_MS, cooldownMs = CREDENTIAL_VERIFY_COOLDOWN_MS) {
1856
- this.maxFailures = maxFailures;
1857
- this.windowMs = windowMs;
1858
- this.cooldownMs = cooldownMs;
1859
- }
1860
- maxFailures;
1861
- windowMs;
1862
- cooldownMs;
1863
- records = /* @__PURE__ */ new Map();
1864
- /** Is this credential id allowed another attempt right now? */
1865
- check(credentialId, now = Date.now()) {
1866
- const record = this.records.get(credentialId);
1867
- if (record === void 0) return { allowed: true, retryAfterMs: 0 };
1868
- if (record.blockedUntil > now) {
1869
- return { allowed: false, retryAfterMs: record.blockedUntil - now };
1870
- }
1871
- return { allowed: true, retryAfterMs: 0 };
1872
- }
1873
- /** Record a failed verification. Starts the cooldown once the limit is hit. */
1874
- recordFailure(credentialId, now = Date.now()) {
1875
- const record = this.records.get(credentialId);
1876
- if (record === void 0 || now - record.windowStartedAt > this.windowMs) {
1877
- this.evictIfFull();
1878
- this.records.set(credentialId, { failures: 1, windowStartedAt: now, blockedUntil: 0 });
1879
- return;
1880
- }
1881
- record.failures += 1;
1882
- if (record.failures >= this.maxFailures) {
1883
- record.blockedUntil = now + this.cooldownMs;
1884
- record.failures = 0;
1885
- record.windowStartedAt = now;
1886
- }
1887
- }
1888
- /**
1889
- * Clear the counter after a successful verification. A legitimate holder who
1890
- * fluffed a few attempts is not left in a cooldown they cannot exit.
1891
- */
1892
- recordSuccess(credentialId) {
1893
- this.records.delete(credentialId);
1894
- }
1895
- /** Test seam. */
1896
- reset() {
1897
- this.records.clear();
1898
- }
1899
- evictIfFull() {
1900
- if (this.records.size < MAX_TRACKED_IDS) return;
1901
- const oldest = this.records.keys().next();
1902
- if (!oldest.done) this.records.delete(oldest.value);
1903
- }
1904
- };
1905
- var credentialVerifyThrottle = new CredentialVerifyThrottle();
1906
-
1907
- // src/coordination/sqlite-mailbox-rows.ts
1908
- 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*'";
1909
- function withoutAggregateCompletion(message) {
1910
- const stored = {
1911
- ...message,
1912
- completed: message.legacyGlobalCompletion === true
1913
- };
1914
- if (!stored.completed) {
1915
- delete stored.completedBy;
1916
- delete stored.completedAt;
1917
- }
1918
- delete stored.outcome;
1919
- return stored;
1920
- }
1921
- function persistMessage(db, message, legacyGlobalCompletion = false) {
1922
- const stored = { ...message, readBy: { ...message.readBy } };
1923
- delete stored.recipientState;
1924
- delete stored.legacyGlobalCompletion;
1925
- db.prepare(`
1926
- INSERT INTO messages(
1927
- id, from_id, to_id, type, priority, timestamp, completed, completed_at,
1928
- deleted_at, sender_session_id, reply_to, expires_at,
1929
- legacy_global_completion, data
1930
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1931
- ON CONFLICT(id) DO UPDATE SET
1932
- from_id = excluded.from_id,
1933
- to_id = excluded.to_id,
1934
- type = excluded.type,
1935
- priority = excluded.priority,
1936
- timestamp = excluded.timestamp,
1937
- completed = excluded.completed,
1938
- completed_at = excluded.completed_at,
1939
- deleted_at = excluded.deleted_at,
1940
- sender_session_id = excluded.sender_session_id,
1941
- reply_to = excluded.reply_to,
1942
- expires_at = excluded.expires_at,
1943
- legacy_global_completion = excluded.legacy_global_completion,
1944
- data = excluded.data
1945
- `).run(
1946
- message.id,
1947
- message.from,
1948
- message.to,
1949
- message.type,
1950
- message.priority,
1951
- message.timestamp,
1952
- message.completed ? 1 : 0,
1953
- message.completedAt ?? null,
1954
- message.deletedAt ?? null,
1955
- message.senderSessionId ?? null,
1956
- message.replyTo ?? null,
1957
- message.expiresAt ?? null,
1958
- legacyGlobalCompletion ? 1 : 0,
1959
- JSON.stringify(stored)
1960
- );
1961
- }
1962
- function persistReceipt(db, messageId, state) {
1963
- db.prepare(`
1964
- INSERT INTO message_receipts(
1965
- message_id, actor_id, read_at, completed_at, completed_by, outcome
1966
- ) VALUES (?, ?, ?, ?, ?, ?)
1967
- ON CONFLICT(message_id, actor_id) DO UPDATE SET
1968
- read_at = excluded.read_at,
1969
- completed_at = excluded.completed_at,
1970
- completed_by = excluded.completed_by,
1971
- outcome = excluded.outcome
1972
- `).run(
1973
- messageId,
1974
- state.actorId,
1975
- state.readAt ?? null,
1976
- state.completedAt ?? null,
1977
- state.completedBy ?? null,
1978
- state.outcome ?? null
1979
- );
1980
- }
1981
- function materializeMessageRows(db, rows) {
1982
- if (rows.length === 0) return [];
1983
- const receiptRows = [];
1984
- for (const ids of chunk(
1985
- rows.map((row) => row.id),
1986
- 400
1987
- )) {
1988
- const receiptSql = `
1989
- SELECT message_id, actor_id, read_at, completed_at, completed_by, outcome
1990
- FROM message_receipts
1991
- WHERE message_id IN (${ids.map(() => "?").join(", ")})
1992
- `;
1993
- receiptRows.push(...db.prepare(receiptSql).all(...ids));
1994
- }
1995
- const receiptState = /* @__PURE__ */ new Map();
1996
- for (const row of receiptRows) {
1997
- const states = receiptState.get(row.message_id) ?? {};
1998
- states[row.actor_id] = {
1999
- actorId: row.actor_id,
2000
- ...row.read_at !== null ? { readAt: row.read_at } : {},
2001
- ...row.completed_at !== null ? { completedAt: row.completed_at } : {},
2002
- ...row.completed_by !== null ? { completedBy: row.completed_by } : {},
2003
- ...row.outcome !== null ? { outcome: row.outcome } : {}
2004
- };
2005
- receiptState.set(row.message_id, states);
2006
- }
2007
- return rows.map((row) => {
2008
- const base = JSON.parse(row.data);
2009
- const recipientState = receiptState.get(row.id) ?? {};
2010
- const readBy = { ...base.readBy };
2011
- for (const state of Object.values(recipientState)) {
2012
- if (state.readAt !== void 0) readBy[state.actorId] = state.readAt;
2013
- }
2014
- return {
2015
- ...base,
2016
- readBy,
2017
- recipientState,
2018
- ...row.legacy_global_completion === 1 ? { legacyGlobalCompletion: true } : {}
2019
- };
2020
- });
2021
- }
2022
- function deleteMessages(db, ids) {
2023
- for (const chunkIds of chunk(ids, 400)) {
2024
- db.prepare(`DELETE FROM messages WHERE id IN (${chunkIds.map(() => "?").join(", ")})`).run(
2025
- ...chunkIds
2026
- );
2027
- }
2028
- }
2029
- function* chunk(values, size) {
2030
- for (let start = 0; start < values.length; start += size) {
2031
- yield values.slice(start, start + size);
2032
- }
2033
- }
2034
- function persistAgent(db, agent) {
2035
- db.prepare(`
2036
- INSERT INTO agents(
2037
- agent_id, session_id, name, role, status, current_tool, current_task,
2038
- iterations, tool_calls, registered_at, last_seen_at, pid, source
2039
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2040
- ON CONFLICT(agent_id) DO UPDATE SET
2041
- session_id = excluded.session_id,
2042
- name = excluded.name,
2043
- role = excluded.role,
2044
- status = excluded.status,
2045
- current_tool = excluded.current_tool,
2046
- current_task = excluded.current_task,
2047
- iterations = excluded.iterations,
2048
- tool_calls = excluded.tool_calls,
2049
- registered_at = excluded.registered_at,
2050
- last_seen_at = excluded.last_seen_at,
2051
- pid = excluded.pid,
2052
- source = excluded.source
2053
- `).run(
2054
- agent.agentId,
2055
- agent.sessionId,
2056
- agent.name,
2057
- agent.role ?? null,
2058
- agent.status,
2059
- agent.currentTool ?? null,
2060
- agent.currentTask ?? null,
2061
- agent.iterations,
2062
- agent.toolCalls,
2063
- agent.registeredAt,
2064
- agent.lastSeenAt,
2065
- agent.pid,
2066
- agent.source ?? null
2067
- );
2068
- }
2069
- function readAgents(db) {
2070
- const rows = db.prepare("SELECT * FROM agents").all();
2071
- const agents = /* @__PURE__ */ new Map();
2072
- for (const row of rows) {
2073
- const agent = {
2074
- agentId: String(row["agent_id"]),
2075
- sessionId: String(row["session_id"]),
2076
- name: String(row["name"]),
2077
- ...row["role"] !== null ? { role: String(row["role"]) } : {},
2078
- status: row["status"],
2079
- ...row["current_tool"] !== null ? { currentTool: String(row["current_tool"]) } : {},
2080
- ...row["current_task"] !== null ? { currentTask: String(row["current_task"]) } : {},
2081
- iterations: Number(row["iterations"]),
2082
- toolCalls: Number(row["tool_calls"]),
2083
- registeredAt: String(row["registered_at"]),
2084
- lastSeenAt: String(row["last_seen_at"]),
2085
- pid: Number(row["pid"]),
2086
- ...row["source"] !== null ? { source: row["source"] } : {}
2087
- };
2088
- agents.set(agent.agentId, agent);
2089
- }
2090
- return agents;
2091
- }
2092
- function pruneAgents(db, maxAgeMs = AGENT_STALE_MS) {
2093
- const cutoff = new Date(Date.now() - Math.max(0, maxAgeMs)).toISOString();
2094
- const result = db.prepare(`DELETE FROM agents WHERE last_seen_at < ? OR ${MALFORMED_TIMESTAMP}`).run(cutoff);
2095
- return Number(result.changes);
2096
- }
2097
- function persistClient(db, client) {
2098
- db.prepare(`
2099
- INSERT INTO clients(
2100
- client_id, session_id, name, source, registered_at, last_seen_at, pid
2101
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
2102
- ON CONFLICT(client_id) DO UPDATE SET
2103
- session_id = excluded.session_id,
2104
- name = excluded.name,
2105
- source = excluded.source,
2106
- registered_at = excluded.registered_at,
2107
- last_seen_at = excluded.last_seen_at,
2108
- pid = excluded.pid
2109
- `).run(
2110
- client.clientId,
2111
- client.sessionId,
2112
- client.name,
2113
- client.source,
2114
- client.registeredAt,
2115
- client.lastSeenAt,
2116
- client.pid
2117
- );
2118
- }
2119
- function readClients(db) {
2120
- const rows = db.prepare("SELECT * FROM clients").all();
2121
- const clients2 = /* @__PURE__ */ new Map();
2122
- for (const row of rows) {
2123
- const client = {
2124
- clientId: String(row["client_id"]),
2125
- sessionId: String(row["session_id"]),
2126
- name: String(row["name"]),
2127
- source: row["source"],
2128
- registeredAt: String(row["registered_at"]),
2129
- lastSeenAt: String(row["last_seen_at"]),
2130
- pid: Number(row["pid"])
2131
- };
2132
- clients2.set(client.clientId, client);
2133
- }
2134
- return clients2;
2135
- }
2136
- function pruneClients(db) {
2137
- const cutoff = new Date(Date.now() - CLIENT_STALE_MS).toISOString();
2138
- const result = db.prepare(`DELETE FROM clients WHERE last_seen_at < ? OR ${MALFORMED_TIMESTAMP}`).run(cutoff);
2139
- return Number(result.changes);
2140
- }
2141
- function persistCredential(db, credential) {
2142
- db.prepare(`
2143
- INSERT INTO credentials(credential_id, status, principal_id, expires_at, data)
2144
- VALUES (?, ?, ?, ?, ?)
2145
- ON CONFLICT(credential_id) DO UPDATE SET
2146
- status = excluded.status,
2147
- principal_id = excluded.principal_id,
2148
- expires_at = excluded.expires_at,
2149
- data = excluded.data
2150
- `).run(
2151
- credential.credentialId,
2152
- credential.status,
2153
- credential.principalId,
2154
- credential.expiresAt,
2155
- JSON.stringify(credential)
2156
- );
2157
- }
2158
-
2159
- // src/coordination/sqlite-mailbox-credentials.ts
2160
- function credentialGet(db, credentialId) {
2161
- const row = db.prepare("SELECT data FROM credentials WHERE credential_id = ?").get(credentialId);
2162
- return row === void 0 ? null : JSON.parse(row.data);
2163
- }
2164
- function credentialList(db) {
2165
- const rows = db.prepare("SELECT data FROM credentials").all();
2166
- return rows.map((row) => JSON.parse(row.data)).sort((left, right) => {
2167
- if (left.status === "active" && right.status !== "active") return -1;
2168
- if (left.status !== "active" && right.status === "active") return 1;
2169
- return right.issuedAt.localeCompare(left.issuedAt);
2170
- });
2171
- }
2172
- function credentialStatusCounts(db) {
2173
- const rows = db.prepare("SELECT status, COUNT(*) AS count FROM credentials GROUP BY status").all();
2174
- return Object.fromEntries(rows.map((row) => [row.status, row.count]));
2175
- }
2176
- function credentialIssue(db, transaction, options) {
2177
- const now = Date.now();
2178
- const issued = createMailboxCredential(options, now);
2179
- transaction(() => {
2180
- if (options.supersedes !== void 0) {
2181
- const old = credentialGet(db, options.supersedes);
2182
- if (old?.status === "active") {
2183
- old.status = "rotated_out";
2184
- old.statusChangedAt = new Date(now).toISOString();
2185
- old.statusReason = "superseded by rotation";
2186
- old.rotationValidUntil = new Date(now + ROTATION_OVERLAP_MS).toISOString();
2187
- persistCredential(db, old);
2188
- }
2189
- }
2190
- persistCredential(db, issued.credential);
2191
- });
2192
- return issued;
2193
- }
2194
- function credentialVerify(db, credentialId, secret) {
2195
- const decision = credentialVerifyThrottle.check(credentialId);
2196
- if (!decision.allowed) {
2197
- return {
2198
- valid: false,
2199
- reason: `too many failed verification attempts \u2014 retry in ${Math.ceil(decision.retryAfterMs / 1e3)}s`
2200
- };
2201
- }
2202
- const result = verifyMailboxCredential(credentialGet(db, credentialId) ?? void 0, secret);
2203
- if (result.valid) credentialVerifyThrottle.recordSuccess(credentialId);
2204
- else credentialVerifyThrottle.recordFailure(credentialId);
2205
- return result;
2206
- }
2207
- function credentialRevoke(db, credentialId, reason, by) {
2208
- const credential = credentialGet(db, credentialId);
2209
- if (credential === null || credential.status === "revoked") return false;
2210
- credential.status = "revoked";
2211
- credential.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
2212
- credential.statusReason = reason ?? "revoked";
2213
- credential.lastModifiedBy = by;
2214
- persistCredential(db, credential);
2215
- return true;
2216
- }
2217
- function credentialRotate(db, transaction, credentialId, options) {
2218
- const old = credentialGet(db, credentialId);
2219
- if (old === null) return null;
2220
- if (old.status === "revoked") return null;
2221
- return credentialIssue(db, transaction, {
2222
- principalId: old.principalId,
2223
- projectId: old.projectId ?? options?.projectId,
2224
- kind: old.kind,
2225
- capabilities: options?.capabilities ?? old.capabilities,
2226
- ttlMs: options?.ttlMs ?? previousCredentialTtlMs(old),
2227
- notBefore: options?.notBefore,
2228
- supersedes: credentialId,
2229
- issuedBy: options?.issuedBy
2230
- });
2231
- }
2232
- function previousCredentialTtlMs(credential) {
2233
- const issuedAt = new Date(credential.issuedAt).getTime();
2234
- const expiresAt = new Date(credential.expiresAt).getTime();
2235
- const ttlMs = expiresAt - issuedAt;
2236
- return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : MAX_CREDENTIAL_TTL[credential.kind];
2237
- }
2238
-
2239
- // src/coordination/sqlite-mailbox-schema.ts
2240
- import * as fs2 from "node:fs";
2241
- import * as path5 from "node:path";
2242
- import { loadRuntimeDatabaseSync } from "@wrongstack/persistence";
2243
-
2244
- // src/utils/sqlite-warning.ts
2245
- var SQLITE_EXPERIMENTAL_WARNING_RE = /sqlite is an experimental feature/i;
2246
- function isSqliteExperimentalWarning(warning, rest) {
2247
- const message = typeof warning === "string" ? warning : warning instanceof Error ? warning.message : "";
2248
- const typeOrOptions = rest[0];
2249
- 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 : "";
2250
- 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 : "";
2251
- return SQLITE_EXPERIMENTAL_WARNING_RE.test(message) && (warningType === "ExperimentalWarning" || warningCode === "ExperimentalWarning");
2252
- }
2253
- function withSqliteExperimentalWarningSuppressed(run) {
2254
- const originalEmitWarning = process.emitWarning;
2255
- const forwardWarning = originalEmitWarning.bind(process);
2256
- process.emitWarning = ((warning, ...rest) => {
2257
- if (isSqliteExperimentalWarning(warning, rest)) return;
2258
- forwardWarning(warning, ...rest);
2259
- });
2260
- try {
2261
- return run();
2262
- } finally {
2263
- process.emitWarning = originalEmitWarning;
2264
- }
2265
- }
2266
-
2267
- // src/coordination/global-mailbox-paths.ts
2268
- var GLOBAL_MAILBOX_FILE = "_mailbox.jsonl";
2269
- var GLOBAL_MAILBOX_CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
2270
-
2271
- // src/coordination/mailbox-parse-state.ts
2272
- function parseMailboxFile(raw) {
2273
- return createMailboxParseState(raw).projections;
2274
- }
2275
- function createMailboxParseState(raw) {
2276
- const state = {
2277
- messages: [],
2278
- projections: [],
2279
- indexById: /* @__PURE__ */ new Map(),
2280
- receiptsByMessage: /* @__PURE__ */ new Map()
2281
- };
2282
- ingestMailboxChunk(state, raw);
2283
- return state;
2284
- }
2285
- function ingestMailboxChunk(state, chunk2) {
2286
- const firstNewIndex = state.messages.length;
2287
- const ackRecords = [];
2288
- const staleExisting = /* @__PURE__ */ new Set();
2289
- for (const line of chunk2.split(LINE_SEPARATOR)) {
2290
- if (line.trim().length === 0) continue;
2291
- let parsed;
2292
- try {
2293
- parsed = JSON.parse(line);
2294
- } catch {
2295
- continue;
2296
- }
2297
- if (isMailboxReceiptRecordV2(parsed)) {
2298
- const list = state.receiptsByMessage.get(parsed.messageId);
2299
- if (list) list.push(parsed);
2300
- else state.receiptsByMessage.set(parsed.messageId, [parsed]);
2301
- for (const index2 of state.indexById.get(parsed.messageId) ?? []) {
2302
- if (index2 < firstNewIndex) staleExisting.add(index2);
2303
- }
2304
- continue;
2305
- }
2306
- if (isAckRecord(parsed)) {
2307
- ackRecords.push(parsed);
2308
- continue;
2309
- }
2310
- let message;
2311
- try {
2312
- message = parseMailboxMessage(parsed);
2313
- } catch {
2314
- continue;
2315
- }
2316
- const index = state.messages.length;
2317
- state.messages.push(message);
2318
- const indices = state.indexById.get(message.id);
2319
- if (indices) indices.push(index);
2320
- else state.indexById.set(message.id, [index]);
2321
- }
2322
- for (const ack of ackRecords) {
2323
- const indices = state.indexById.get(ack.messageId);
2324
- if (indices === void 0 || indices.length === 0) continue;
2325
- const index = indices[indices.length - 1];
2326
- applyAckToMessage(state.messages[index], ack);
2327
- if (index < firstNewIndex) staleExisting.add(index);
2328
- }
2329
- for (const index of staleExisting) {
2330
- const message = state.messages[index];
2331
- state.projections[index] = materializeMessage(
2332
- message,
2333
- state.receiptsByMessage.get(message.id) ?? []
2334
- );
2335
- }
2336
- for (let index = firstNewIndex; index < state.messages.length; index++) {
2337
- const message = state.messages[index];
2338
- state.projections.push(
2339
- materializeMessage(message, state.receiptsByMessage.get(message.id) ?? [])
2340
- );
2341
- }
2342
- }
2343
-
2344
- // src/coordination/mailbox-registry-codec.ts
2345
- function parseAgentRegistryEntry(value) {
2346
- if (typeof value !== "object" || value === null) return null;
2347
- const v = value;
2348
- if (typeof v.agentId !== "string") return null;
2349
- if (typeof v.sessionId !== "string") return null;
2350
- const statuses = ["idle", "busy", "running", "streaming", "waiting_user", "error"];
2351
- const status = typeof v.status === "string" && statuses.includes(v.status) ? v.status : "idle";
2352
- return {
2353
- ...v,
2354
- agentId: v.agentId,
2355
- sessionId: v.sessionId,
2356
- name: typeof v.name === "string" ? v.name : v.agentId,
2357
- registeredAt: typeof v.registeredAt === "string" ? v.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2358
- lastSeenAt: typeof v.lastSeenAt === "string" ? v.lastSeenAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2359
- iterations: typeof v.iterations === "number" && Number.isFinite(v.iterations) ? v.iterations : 0,
2360
- toolCalls: typeof v.toolCalls === "number" && Number.isFinite(v.toolCalls) ? v.toolCalls : 0,
2361
- pid: typeof v.pid === "number" && Number.isFinite(v.pid) ? v.pid : 0,
2362
- status
2363
- };
2364
- }
2365
- function parseClientRegistryEntry(value) {
2366
- if (typeof value !== "object" || value === null) return null;
2367
- const v = value;
2368
- if (typeof v.clientId !== "string") return null;
2369
- if (typeof v.sessionId !== "string") return null;
2370
- const sources = ["repl", "tui", "webui", "http"];
2371
- const source = typeof v.source === "string" && sources.includes(v.source) ? v.source : "http";
2372
- return {
2373
- ...v,
2374
- clientId: v.clientId,
2375
- sessionId: v.sessionId,
2376
- name: typeof v.name === "string" ? v.name : v.clientId,
2377
- registeredAt: typeof v.registeredAt === "string" ? v.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2378
- lastSeenAt: typeof v.lastSeenAt === "string" ? v.lastSeenAt : (/* @__PURE__ */ new Date(0)).toISOString(),
2379
- pid: typeof v.pid === "number" && Number.isFinite(v.pid) ? v.pid : 0,
2380
- source
2381
- };
2382
- }
2383
-
2384
- // src/coordination/sqlite-mailbox-schema.ts
2385
- var SQLITE_MAILBOX_SCHEMA_VERSION = 2;
2386
- var DatabaseSyncCtor;
2387
- function loadDatabaseSync() {
2388
- if (DatabaseSyncCtor) return DatabaseSyncCtor;
2389
- return withSqliteExperimentalWarningSuppressed(() => {
2390
- DatabaseSyncCtor = loadRuntimeDatabaseSync();
2391
- return DatabaseSyncCtor;
2392
- });
2393
- }
2394
- function initializeSchema(ctx) {
2395
- const { db } = ctx;
2396
- db.exec(`
2397
- CREATE TABLE IF NOT EXISTS mailbox_meta (
2398
- key TEXT PRIMARY KEY,
2399
- value TEXT NOT NULL
2400
- );
2401
- CREATE TABLE IF NOT EXISTS messages (
2402
- id TEXT PRIMARY KEY,
2403
- from_id TEXT NOT NULL,
2404
- to_id TEXT NOT NULL,
2405
- type TEXT NOT NULL,
2406
- priority TEXT NOT NULL,
2407
- timestamp TEXT NOT NULL,
2408
- completed INTEGER NOT NULL DEFAULT 0,
2409
- completed_at TEXT,
2410
- deleted_at TEXT,
2411
- sender_session_id TEXT,
2412
- reply_to TEXT,
2413
- expires_at TEXT,
2414
- legacy_global_completion INTEGER NOT NULL DEFAULT 0,
2415
- data TEXT NOT NULL
2416
- );
2417
- CREATE TABLE IF NOT EXISTS message_receipts (
2418
- message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
2419
- actor_id TEXT NOT NULL,
2420
- read_at TEXT,
2421
- completed_at TEXT,
2422
- completed_by TEXT,
2423
- outcome TEXT,
2424
- PRIMARY KEY (message_id, actor_id)
2425
- );
2426
- CREATE TABLE IF NOT EXISTS agents (
2427
- agent_id TEXT PRIMARY KEY,
2428
- session_id TEXT NOT NULL,
2429
- name TEXT NOT NULL,
2430
- role TEXT,
2431
- status TEXT NOT NULL,
2432
- current_tool TEXT,
2433
- current_task TEXT,
2434
- iterations INTEGER NOT NULL,
2435
- tool_calls INTEGER NOT NULL,
2436
- registered_at TEXT NOT NULL,
2437
- last_seen_at TEXT NOT NULL,
2438
- pid INTEGER NOT NULL,
2439
- source TEXT
2440
- );
2441
- CREATE TABLE IF NOT EXISTS clients (
2442
- client_id TEXT PRIMARY KEY,
2443
- session_id TEXT NOT NULL,
2444
- name TEXT NOT NULL,
2445
- source TEXT NOT NULL,
2446
- registered_at TEXT NOT NULL,
2447
- last_seen_at TEXT NOT NULL,
2448
- pid INTEGER NOT NULL
2449
- );
2450
- CREATE TABLE IF NOT EXISTS credentials (
2451
- credential_id TEXT PRIMARY KEY,
2452
- status TEXT NOT NULL,
2453
- principal_id TEXT NOT NULL,
2454
- expires_at TEXT NOT NULL,
2455
- data TEXT NOT NULL
2456
- );
2457
- CREATE INDEX IF NOT EXISTS idx_messages_to_timestamp ON messages(to_id, timestamp DESC);
2458
- CREATE INDEX IF NOT EXISTS idx_messages_from_timestamp ON messages(from_id, timestamp DESC);
2459
- CREATE INDEX IF NOT EXISTS idx_messages_type_timestamp ON messages(type, timestamp DESC);
2460
- CREATE INDEX IF NOT EXISTS idx_messages_deleted_at ON messages(deleted_at);
2461
- CREATE INDEX IF NOT EXISTS idx_messages_session_timestamp ON messages(sender_session_id, timestamp DESC);
2462
- CREATE INDEX IF NOT EXISTS idx_messages_reply_timestamp ON messages(reply_to, timestamp DESC);
2463
- CREATE INDEX IF NOT EXISTS idx_receipts_actor ON message_receipts(actor_id, message_id);
2464
- CREATE INDEX IF NOT EXISTS idx_agents_last_seen ON agents(last_seen_at);
2465
- CREATE INDEX IF NOT EXISTS idx_clients_last_seen ON clients(last_seen_at);
2466
- CREATE INDEX IF NOT EXISTS idx_credentials_status ON credentials(status, expires_at);
2467
- `);
2468
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?) ON CONFLICT(key) DO NOTHING").run(
2469
- "schema_version",
2470
- String(SQLITE_MAILBOX_SCHEMA_VERSION)
2471
- );
2472
- const schema = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("schema_version");
2473
- const foundVersion = Number(schema?.value);
2474
- if (!Number.isInteger(foundVersion) || foundVersion < 1 || foundVersion > SQLITE_MAILBOX_SCHEMA_VERSION) {
2475
- throw new Error(
2476
- `Unsupported mailbox SQLite schema ${schema?.value ?? "missing"}; this build supports ${SQLITE_MAILBOX_SCHEMA_VERSION}`
2477
- );
2478
- }
2479
- if (foundVersion < SQLITE_MAILBOX_SCHEMA_VERSION) {
2480
- db.prepare("UPDATE mailbox_meta SET value = ? WHERE key = ?").run(
2481
- String(SQLITE_MAILBOX_SCHEMA_VERSION),
2482
- "schema_version"
2483
- );
2484
- }
2485
- migrateLegacyCredentials(ctx);
2486
- }
2487
- function migrateLegacyCredentials(ctx) {
2488
- const { db } = ctx;
2489
- const marker = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("legacy_credentials_imported");
2490
- if (marker !== void 0) return;
2491
- const legacyPath = path5.join(ctx.projectDir, CREDENTIAL_STORE_FILE);
2492
- const credentials = [];
2493
- try {
2494
- for (const line of fs2.readFileSync(legacyPath, "utf8").split(/\r?\n/u)) {
2495
- if (!line.trim()) continue;
2496
- try {
2497
- const credential = JSON.parse(line);
2498
- if (typeof credential.credentialId === "string" && typeof credential.verifier === "string" && typeof credential.principalId === "string")
2499
- credentials.push(credential);
2500
- } catch {
2501
- }
2502
- }
2503
- } catch (error) {
2504
- const code = error.code;
2505
- if (code !== "ENOENT") throw error;
2506
- }
2507
- ctx.transaction(() => {
2508
- for (const credential of credentials) persistCredential(db, credential);
2509
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?)").run(
2510
- "legacy_credentials_imported",
2511
- (/* @__PURE__ */ new Date()).toISOString()
2512
- );
2513
- });
2514
- }
2515
- function migrateLegacyFiles(ctx) {
2516
- const { db } = ctx;
2517
- const marker = db.prepare("SELECT value FROM mailbox_meta WHERE key = ?").get("legacy_files_imported");
2518
- if (marker !== void 0) return;
2519
- const messages = readLegacyMessages(ctx.projectDir);
2520
- const agents = readLegacyRegistry(
2521
- path5.join(ctx.projectDir, "_mailbox.registry.json"),
2522
- parseAgentRegistryEntry
2523
- );
2524
- const clients2 = readLegacyRegistry(
2525
- path5.join(ctx.projectDir, GLOBAL_MAILBOX_CLIENT_REGISTRY_FILE),
2526
- parseClientRegistryEntry
2527
- );
2528
- ctx.transaction(() => {
2529
- for (const message of messages) {
2530
- const projection = message;
2531
- persistMessage(db, message, projection.legacyGlobalCompletion === true);
2532
- for (const state of Object.values(projection.recipientState ?? {})) {
2533
- persistReceipt(db, message.id, state);
2534
- }
2535
- }
2536
- for (const agent of agents.values()) persistAgent(db, agent);
2537
- for (const client of clients2.values()) persistClient(db, client);
2538
- db.prepare("INSERT INTO mailbox_meta(key, value) VALUES (?, ?)").run(
2539
- "legacy_files_imported",
2540
- (/* @__PURE__ */ new Date()).toISOString()
2541
- );
2542
- });
2543
- }
2544
- function readLegacyMessages(projectDir2) {
2545
- try {
2546
- return parseMailboxFile(fs2.readFileSync(path5.join(projectDir2, GLOBAL_MAILBOX_FILE), "utf8"));
2547
- } catch (error) {
2548
- if (error.code === "ENOENT") return [];
2549
- throw error;
2550
- }
2551
- }
2552
- function readLegacyRegistry(filePath, parseEntry) {
2553
- try {
2554
- const raw = JSON.parse(fs2.readFileSync(filePath, "utf8"));
2555
- const result = /* @__PURE__ */ new Map();
2556
- for (const [id, value] of Object.entries(raw)) {
2557
- const parsed = parseEntry(value);
2558
- if (parsed !== null) result.set(id, parsed);
2559
- }
2560
- return result;
2561
- } catch (error) {
2562
- if (error.code === "ENOENT") return /* @__PURE__ */ new Map();
2563
- throw error;
2564
- }
2565
- }
2566
-
2567
- // src/coordination/sqlite-mailbox.ts
2568
- var SQLITE_MAILBOX_FILE = "_mailbox.sqlite";
2569
- var HEARTBEAT_TRACKING_MAX_ENTRIES = 512;
2570
- var HEARTBEAT_TRACKING_TTL_MS = 30 * 6e4;
2571
- var SqliteMailbox = class {
2572
- constructor(projectDir2, events2, eventEmitter2) {
2573
- this.projectDir = projectDir2;
2574
- fs3.mkdirSync(projectDir2, { recursive: true });
2575
- this.databasePath = path6.join(projectDir2, SQLITE_MAILBOX_FILE);
2576
- this.messagePath = this.databasePath;
2577
- this.events = events2;
2578
- this.eventEmitter = eventEmitter2;
2579
- const Database = loadDatabaseSync();
2580
- this.db = new Database(this.databasePath);
2581
- this.db.exec("PRAGMA journal_mode = WAL");
2582
- this.db.exec("PRAGMA synchronous = NORMAL");
2583
- this.db.exec("PRAGMA foreign_keys = ON");
2584
- this.db.exec("PRAGMA busy_timeout = 5000");
2585
- initializeSchema(this.schemaCtx());
2586
- migrateLegacyFiles(this.schemaCtx());
2587
- }
2588
- projectDir;
2589
- databasePath;
2590
- /** Compatibility alias used by project-server health/status consumers. */
2591
- messagePath;
2592
- eventEmitter;
2593
- db;
2594
- events;
2595
- lastHeartbeat = /* @__PURE__ */ new Map();
2596
- lastClientHeartbeat = /* @__PURE__ */ new Map();
2597
- autoCompactTimer = null;
2598
- autoCompactInFlight;
2599
- closed = false;
2600
- stmt(sql) {
2601
- return this.db.prepare(sql);
2602
- }
2603
- transaction(run) {
2604
- this.db.exec("BEGIN IMMEDIATE");
2605
- try {
2606
- const result = run();
2607
- this.db.exec("COMMIT");
2608
- return result;
2609
- } catch (error) {
2610
- this.db.exec("ROLLBACK");
2611
- throw error;
2612
- }
2613
- }
2614
- /** Bundle of store state the schema/migration module operates on. */
2615
- schemaCtx() {
2616
- return {
2617
- db: this.db,
2618
- projectDir: this.projectDir,
2619
- transaction: (run) => this.transaction(run)
2620
- };
2621
- }
2622
- persistMessage(message, legacyGlobalCompletion = false) {
2623
- persistMessage(this.db, message, legacyGlobalCompletion);
2624
- }
2625
- persistReceipt(messageId, state) {
2626
- persistReceipt(this.db, messageId, state);
2627
- }
2628
- materializeMessageRows(rows) {
2629
- return materializeMessageRows(this.db, rows);
2630
- }
2631
- readMessages() {
2632
- const rows = this.stmt(
2633
- "SELECT id, data, legacy_global_completion FROM messages"
2634
- ).all();
2635
- return this.materializeMessageRows(rows);
2636
- }
2637
- findMessage(messageId) {
2638
- const row = this.stmt(
2639
- "SELECT id, data, legacy_global_completion FROM messages WHERE id = ?"
2640
- ).get(messageId);
2641
- return row === void 0 ? void 0 : this.materializeMessageRows([row])[0];
2642
- }
2643
- async send(input) {
2644
- return this.sendMessage(input, false);
2645
- }
2646
- async sendRuntimeControl(input) {
2647
- return this.sendMessage({ ...input, type: "control" }, true);
2648
- }
2649
- async sendMessage(input, allowRuntimeControl) {
2650
- const type = normalizeMailboxMessageType(input.type);
2651
- const to = normalizeRecipient(input.to, input.senderSessionId);
2652
- if (!(allowRuntimeControl && type === "control")) validateSendType(type, to);
2653
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2654
- const message = {
2655
- id: randomUUID2(),
2656
- from: input.from,
2657
- to,
2658
- type,
2659
- ...input.audience !== void 0 && input.audience !== "all" ? { audience: input.audience } : {},
2660
- subject: input.subject,
2661
- body: input.body,
2662
- priority: input.priority ?? "normal",
2663
- readBy: {},
2664
- completed: false,
2665
- timestamp,
2666
- ...input.replyTo !== void 0 ? { replyTo: input.replyTo } : {},
2667
- ...input.taskContext !== void 0 ? { taskContext: input.taskContext } : {},
2668
- ...input.senderSessionId !== void 0 ? { senderSessionId: input.senderSessionId } : {},
2669
- ...input.sessionAffinity !== void 0 ? { sessionAffinity: input.sessionAffinity } : {},
2670
- ...input.ttlMs !== void 0 ? { expiresAt: new Date(Date.now() + input.ttlMs).toISOString() } : {}
2671
- };
2672
- this.persistMessage(message);
2673
- this.events?.emitCustom("mailbox.message_sent", {
2674
- messageId: message.id,
2675
- from: message.from,
2676
- to: message.to,
2677
- type: message.type,
2678
- subject: message.subject
2679
- });
2680
- this.eventEmitter?.emit({
2681
- type: "message.sent",
2682
- messageId: message.id,
2683
- from: message.from,
2684
- to: message.to,
2685
- audience: message.audience,
2686
- timestamp
2687
- });
2688
- return message;
2689
- }
2690
- async query(query) {
2691
- const type = query.type === void 0 ? void 0 : normalizeMailboxMessageType(query.type);
2692
- const priorityRank = { low: 0, normal: 1, high: 2 };
2693
- const minimumRank = query.minPriority === void 0 ? 0 : priorityRank[query.minPriority];
2694
- if (query.ids !== void 0 && query.ids.length === 0) return [];
2695
- const statuses = query.unreadBy === void 0 ? await this.getAgentStatuses() : void 0;
2696
- const where = [];
2697
- const params = [];
2698
- if (query.ids !== void 0) {
2699
- where.push(`id IN (${query.ids.map(() => "?").join(", ")})`);
2700
- params.push(...query.ids);
2701
- }
2702
- if (query.to !== void 0) {
2703
- where.push("(to_id = ? OR to_id = ?)");
2704
- params.push(query.to, "*");
2705
- }
2706
- if (query.from !== void 0) {
2707
- where.push("from_id = ?");
2708
- params.push(query.from);
2709
- }
2710
- if (query.sessionId !== void 0) {
2711
- where.push("sender_session_id = ?");
2712
- params.push(query.sessionId);
2713
- }
2714
- if (type !== void 0) {
2715
- where.push("type = ?");
2716
- params.push(type);
2717
- }
2718
- if (query.minPriority !== void 0) {
2719
- where.push(`CASE priority WHEN 'high' THEN 2 WHEN 'low' THEN 0 ELSE 1 END >= ?`);
2720
- params.push(minimumRank);
2721
- }
2722
- if (query.since !== void 0) {
2723
- where.push("timestamp > ?");
2724
- params.push(query.since);
2725
- }
2726
- if (!query.includeDeleted) where.push("deleted_at IS NULL");
2727
- if (query.replyTo !== void 0) {
2728
- where.push("reply_to = ?");
2729
- params.push(query.replyTo);
2730
- }
2731
- if (query.unreadBy !== void 0) {
2732
- if (!isMailboxLeader(query.unreadBy, query.readerRole)) {
2733
- where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
2734
- }
2735
- if (!query.incompleteOnly) {
2736
- where.push(`NOT EXISTS (
2737
- SELECT 1 FROM message_receipts AS unread_receipt
2738
- WHERE unread_receipt.message_id = messages.id
2739
- AND unread_receipt.actor_id = ?
2740
- AND unread_receipt.read_at IS NOT NULL
2741
- )`);
2742
- params.push(query.unreadBy);
2743
- where.push(`NOT EXISTS (
2744
- SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
2745
- WHERE legacy_read.key = ?
2746
- )`);
2747
- params.push(query.unreadBy);
2748
- } else {
2749
- where.push("legacy_global_completion = 0");
2750
- where.push(`NOT EXISTS (
2751
- SELECT 1 FROM message_receipts AS completed_receipt
2752
- WHERE completed_receipt.message_id = messages.id
2753
- AND completed_receipt.actor_id = ?
2754
- AND completed_receipt.completed_at IS NOT NULL
2755
- )`);
2756
- params.push(query.unreadBy);
2757
- where.push(`(
2758
- completed = 0 OR EXISTS (
2759
- SELECT 1 FROM message_receipts AS any_receipt
2760
- WHERE any_receipt.message_id = messages.id
2761
- )
2762
- )`);
2763
- }
2764
- }
2765
- const canPreLimit = !query.incompleteOnly || query.unreadBy !== void 0;
2766
- let sql = "SELECT id, data, legacy_global_completion FROM messages";
2767
- if (where.length > 0) sql += ` WHERE ${where.join(" AND ")}`;
2768
- sql += " ORDER BY timestamp DESC, rowid DESC";
2769
- if (canPreLimit) {
2770
- sql += " LIMIT ?";
2771
- params.push(query.limit ?? 50);
2772
- }
2773
- const rows = this.stmt(sql).all(...params);
2774
- const idFilter = query.ids === void 0 ? void 0 : new Set(query.ids);
2775
- const messages = this.materializeMessageRows(rows).filter((message) => {
2776
- if (idFilter !== void 0 && !idFilter.has(message.id)) return false;
2777
- if (query.to !== void 0 && message.to !== query.to && message.to !== "*") return false;
2778
- if (query.from !== void 0 && message.from !== query.from) return false;
2779
- if (query.sessionId !== void 0 && message.senderSessionId !== query.sessionId)
2780
- return false;
2781
- if (query.unreadBy !== void 0 && !isMailboxMessageVisibleTo(message, query.unreadBy, query.readerRole))
2782
- return false;
2783
- if (!query.incompleteOnly && query.unreadBy !== void 0 && query.unreadBy in message.readBy)
2784
- return false;
2785
- if (query.incompleteOnly && (query.unreadBy === void 0 ? projectMailboxCompletion(message, void 0, statuses).completed : isMessageCompletedForActor(message, query.unreadBy)))
2786
- return false;
2787
- if (type !== void 0 && message.type !== type) return false;
2788
- if (priorityRank[message.priority] < minimumRank) return false;
2789
- if (query.since !== void 0 && message.timestamp <= query.since) return false;
2790
- if (!query.includeDeleted && message.deletedAt !== void 0) return false;
2791
- if (query.replyTo !== void 0 && message.replyTo !== query.replyTo) return false;
2792
- return true;
2793
- });
2794
- messages.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
2795
- return messages.slice(0, query.limit ?? 50).map((message) => {
2796
- const copy = {
2797
- ...projectMailboxCompletion(message, query.unreadBy, statuses),
2798
- readBy: { ...message.readBy }
2799
- };
2800
- if (!query.includeReceiptState) {
2801
- delete copy.recipientState;
2802
- delete copy.legacyGlobalCompletion;
2803
- }
2804
- return copy;
2805
- });
2806
- }
2807
- async ack(input) {
2808
- const results = await this.ackMany({ acks: [input] });
2809
- return results[0] ?? null;
2810
- }
2811
- async ackMany(input) {
2812
- if (input.acks.length === 0) return [];
2813
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2814
- const changed = /* @__PURE__ */ new Set();
2815
- const updated = this.transaction(() => {
2816
- const results = [];
2817
- for (const ack of input.acks) {
2818
- const message = this.findMessage(ack.messageId);
2819
- if (message === void 0) continue;
2820
- const current = message.recipientState[ack.readerId] ?? { actorId: ack.readerId };
2821
- const state = { ...current };
2822
- let didChange = false;
2823
- if (ack.read !== false && state.readAt === void 0) {
2824
- state.readAt = timestamp;
2825
- message.readBy[ack.readerId] = timestamp;
2826
- didChange = true;
2827
- }
2828
- if (ack.completed === true && state.completedAt === void 0 && message.legacyGlobalCompletion !== true) {
2829
- state.completedAt = timestamp;
2830
- state.completedBy = ack.readerId;
2831
- didChange = true;
2832
- }
2833
- if (ack.read === false && ack.completed === false && state.completedAt !== void 0) {
2834
- delete state.completedAt;
2835
- delete state.completedBy;
2836
- didChange = true;
2837
- }
2838
- if (ack.outcome !== void 0 && state.outcome !== ack.outcome) {
2839
- state.outcome = ack.outcome;
2840
- didChange = true;
2841
- }
2842
- message.recipientState = {
2843
- ...message.recipientState,
2844
- [ack.readerId]: state
2845
- };
2846
- const actorCompleted = state.completedAt !== void 0;
2847
- message.completed = message.legacyGlobalCompletion === true || actorCompleted;
2848
- if (actorCompleted) {
2849
- message.completedBy = state.completedBy ?? ack.readerId;
2850
- message.completedAt = state.completedAt;
2851
- } else if (message.legacyGlobalCompletion !== true) {
2852
- delete message.completedBy;
2853
- delete message.completedAt;
2854
- }
2855
- message.outcome = state.outcome;
2856
- if (didChange) {
2857
- this.persistReceipt(message.id, state);
2858
- this.persistMessage(
2859
- isFanOutRecipient(message.to) ? withoutAggregateCompletion(message) : message,
2860
- message.legacyGlobalCompletion === true
2861
- );
2862
- changed.add(message.id);
2863
- }
2864
- results.push({ ...message, readBy: { ...message.readBy } });
2865
- }
2866
- return results;
2867
- });
2868
- for (const message of updated) {
2869
- if (!changed.has(message.id)) continue;
2870
- this.eventEmitter?.emit({
2871
- type: "message.acked",
2872
- messageId: message.id,
2873
- from: message.from,
2874
- to: message.to,
2875
- audience: message.audience,
2876
- timestamp
2877
- });
2878
- }
2879
- return updated;
2880
- }
2881
- /**
2882
- * Count the messages this actor has neither read nor completed.
2883
- *
2884
- * Pushed into SQL rather than filtering `readMessages()`. The pre-tool hook
2885
- * asks for this repeatedly — `mailbox-hooks.ts` throttles it to once a
2886
- * second, which bounds the frequency but not the cost — and the JS form
2887
- * materialized EVERY row in `messages`, joined the whole `message_receipts`
2888
- * table, and folded per-actor receipt state across all of them just to
2889
- * return an integer.
2890
- *
2891
- * The predicate is deliberately the same one {@link query} builds for
2892
- * `{ unreadBy, incompleteOnly }`, because it has to agree exactly with
2893
- * `isMessageCompletedForActor` and `isMailboxMessageVisibleTo`:
2894
- *
2895
- * - **unread** — no per-actor receipt carrying `read_at`, and no legacy
2896
- * `readBy` key. Both are checked: `ackMany` writes the receipt row AND
2897
- * mirrors the timestamp into the message's `readBy` JSON.
2898
- * - **incomplete** — not `legacy_global_completion`, no per-actor receipt
2899
- * carrying `completed_at`, and the aggregate `completed` flag counts only
2900
- * when the message has no receipts at all (once any actor has a receipt,
2901
- * completion is per-actor and the aggregate flag is not authoritative).
2902
- * - **audience** — `leaders` mail is invisible unless the actor's base
2903
- * identity is `leader`. This call path carries no role, matching the
2904
- * `isMailboxMessageVisibleTo(message, forAgentId)` it replaces.
2905
- */
2906
- async unreadCount(forAgentId, sessionId) {
2907
- const sessionAddress = sessionId === void 0 ? void 0 : sessionRecipient(sessionId);
2908
- const where = [];
2909
- const params = [];
2910
- const recipients = ["to_id = ?", "to_id = '*'"];
2911
- params.push(forAgentId);
2912
- if (sessionAddress !== void 0) {
2913
- recipients.push("to_id = ?");
2914
- params.push(sessionAddress);
2915
- }
2916
- where.push(`(${recipients.join(" OR ")})`);
2917
- where.push("deleted_at IS NULL");
2918
- if (!isMailboxLeader(forAgentId)) {
2919
- where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
2920
- }
2921
- where.push(`NOT EXISTS (
2922
- SELECT 1 FROM message_receipts AS read_receipt
2923
- WHERE read_receipt.message_id = messages.id
2924
- AND read_receipt.actor_id = ?
2925
- AND read_receipt.read_at IS NOT NULL
2926
- )`);
2927
- params.push(forAgentId);
2928
- where.push(`NOT EXISTS (
2929
- SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
2930
- WHERE legacy_read.key = ?
2931
- )`);
2932
- params.push(forAgentId);
2933
- where.push("legacy_global_completion = 0");
2934
- where.push(`NOT EXISTS (
2935
- SELECT 1 FROM message_receipts AS completed_receipt
2936
- WHERE completed_receipt.message_id = messages.id
2937
- AND completed_receipt.actor_id = ?
2938
- AND completed_receipt.completed_at IS NOT NULL
2939
- )`);
2940
- params.push(forAgentId);
2941
- where.push(`(
2942
- completed = 0 OR EXISTS (
2943
- SELECT 1 FROM message_receipts AS any_receipt
2944
- WHERE any_receipt.message_id = messages.id
2945
- )
2946
- )`);
2947
- const row = this.stmt(
2948
- `SELECT COUNT(*) AS total FROM messages WHERE ${where.join(" AND ")}`
2949
- ).get(...params);
2950
- return Number(row?.total ?? 0);
2951
- }
2952
- async softDelete(mailId, by) {
2953
- const message = this.findMessage(mailId);
2954
- if (message === void 0) return null;
2955
- if (message.deletedAt !== void 0) return { ...message, readBy: { ...message.readBy } };
2956
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2957
- message.deletedAt = timestamp;
2958
- message.deletedBy = by;
2959
- const previousState = message.recipientState[by] ?? { actorId: by };
2960
- const state = {
2961
- ...previousState,
2962
- readAt: previousState.readAt ?? timestamp
2963
- };
2964
- message.readBy[by] = state.readAt;
2965
- message.recipientState = { ...message.recipientState, [by]: state };
2966
- this.transaction(() => {
2967
- this.persistReceipt(message.id, state);
2968
- this.persistMessage(message, message.legacyGlobalCompletion === true);
2969
- });
2970
- this.eventEmitter?.emit({
2971
- type: "message.deleted",
2972
- messageId: message.id,
2973
- from: message.from,
2974
- to: message.to,
2975
- audience: message.audience,
2976
- timestamp
2977
- });
2978
- return { ...message, readBy: { ...message.readBy } };
2979
- }
2980
- async restore(mailId) {
2981
- const message = this.findMessage(mailId);
2982
- if (message === void 0) return null;
2983
- if (message.deletedAt === void 0 && message.deletedBy === void 0) {
2984
- return { ...message, readBy: { ...message.readBy } };
2985
- }
2986
- delete message.deletedAt;
2987
- delete message.deletedBy;
2988
- this.persistMessage(message, message.legacyGlobalCompletion === true);
2989
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2990
- this.eventEmitter?.emit({
2991
- type: "message.restored",
2992
- messageId: message.id,
2993
- from: message.from,
2994
- to: message.to,
2995
- audience: message.audience,
2996
- timestamp
2997
- });
2998
- return { ...message, readBy: { ...message.readBy } };
2999
- }
3000
- persistAgent(agent) {
3001
- persistAgent(this.db, agent);
3002
- }
3003
- readAgents() {
3004
- return readAgents(this.db);
3005
- }
3006
- pruneAgents(maxAgeMs = AGENT_STALE_MS) {
3007
- return pruneAgents(this.db, maxAgeMs);
3008
- }
3009
- async registerAgent(input) {
3010
- this.pruneAgents();
3011
- const now = (/* @__PURE__ */ new Date()).toISOString();
3012
- this.persistAgent({
3013
- agentId: input.agentId,
3014
- sessionId: input.sessionId,
3015
- name: input.name,
3016
- ...input.role !== void 0 ? { role: input.role } : {},
3017
- status: "idle",
3018
- iterations: 0,
3019
- toolCalls: 0,
3020
- registeredAt: now,
3021
- lastSeenAt: now,
3022
- pid: input.pid ?? process.pid,
3023
- ...input.source !== void 0 ? { source: input.source } : {}
3024
- });
3025
- this.events?.emitCustom("mailbox.agent_registered", {
3026
- agentId: input.agentId,
3027
- sessionId: input.sessionId,
3028
- name: input.name,
3029
- role: input.role,
3030
- source: input.source
3031
- });
3032
- }
3033
- /**
3034
- * Throttle bookkeeping only: `agentId`/`clientId` -> last accepted heartbeat.
3035
- *
3036
- * Entries are deleted on a clean deregister, but a crashed or forcibly killed
3037
- * peer never deregisters, so over a long-lived daemon's life these maps grew
3038
- * with every distinct id ever seen. Dropping a stale entry is free: the next
3039
- * heartbeat from that id simply is not throttled and writes once more.
3040
- */
3041
- pruneHeartbeats(map, nowMs) {
3042
- for (const [id, at] of map) {
3043
- if (nowMs - at > HEARTBEAT_TRACKING_TTL_MS) map.delete(id);
3044
- }
3045
- while (map.size > HEARTBEAT_TRACKING_MAX_ENTRIES) {
3046
- const oldest = map.keys().next().value;
3047
- if (oldest === void 0) break;
3048
- map.delete(oldest);
3049
- }
3050
- }
3051
- async deregisterAgent(agentId) {
3052
- this.stmt("DELETE FROM agents WHERE agent_id = ?").run(agentId);
3053
- this.lastHeartbeat.delete(agentId);
3054
- this.events?.emitCustom("mailbox.agent_deregistered", { agentId });
3055
- }
3056
- async heartbeat(input) {
3057
- const nowMs = Date.now();
3058
- if (nowMs - (this.lastHeartbeat.get(input.agentId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
3059
- this.lastHeartbeat.set(input.agentId, nowMs);
3060
- this.pruneHeartbeats(this.lastHeartbeat, nowMs);
3061
- this.pruneAgents();
3062
- const agent = this.readAgents().get(input.agentId);
3063
- if (agent !== void 0) {
3064
- agent.lastSeenAt = new Date(nowMs).toISOString();
3065
- if (input.status !== void 0) agent.status = input.status;
3066
- if (input.currentTool !== void 0) agent.currentTool = input.currentTool;
3067
- if (input.currentTask !== void 0) agent.currentTask = input.currentTask;
3068
- if (input.iterations !== void 0) agent.iterations = input.iterations;
3069
- if (input.toolCalls !== void 0) agent.toolCalls = input.toolCalls;
3070
- this.persistAgent(agent);
3071
- }
3072
- this.events?.emitCustom("mailbox.agent_heartbeat", {
3073
- agentId: input.agentId,
3074
- status: input.status,
3075
- currentTool: input.currentTool,
3076
- currentTask: input.currentTask
3077
- });
3078
- }
3079
- async getAgentStatuses() {
3080
- this.pruneAgents();
3081
- return mapRegisteredAgentsToStatuses(this.readAgents(), Date.now(), AGENT_STALE_MS);
3082
- }
3083
- async purgeAgents(maxAgeMs = AGENT_STALE_MS) {
3084
- return this.pruneAgents(maxAgeMs);
3085
- }
3086
- async getOnlineAgents() {
3087
- return (await this.getAgentStatuses()).filter((agent) => agent.online);
3088
- }
3089
- persistClient(client) {
3090
- persistClient(this.db, client);
3091
- }
3092
- readClients() {
3093
- return readClients(this.db);
3094
- }
3095
- pruneClientsInPlace() {
3096
- return pruneClients(this.db);
3097
- }
3098
- async registerClient(input) {
3099
- this.pruneClientsInPlace();
3100
- const now = (/* @__PURE__ */ new Date()).toISOString();
3101
- this.persistClient({
3102
- clientId: input.clientId,
3103
- sessionId: input.sessionId,
3104
- name: input.name,
3105
- source: input.source,
3106
- registeredAt: now,
3107
- lastSeenAt: now,
3108
- pid: input.pid ?? process.pid
3109
- });
3110
- this.events?.emitCustom("mailbox.client_registered", {
3111
- clientId: input.clientId,
3112
- sessionId: input.sessionId,
3113
- name: input.name,
3114
- source: input.source
3115
- });
3116
- }
3117
- async deregisterClient(clientId) {
3118
- this.stmt("DELETE FROM clients WHERE client_id = ?").run(clientId);
3119
- this.lastClientHeartbeat.delete(clientId);
3120
- this.events?.emitCustom("mailbox.client_deregistered", { clientId });
3121
- }
3122
- async clientHeartbeat(input) {
3123
- const nowMs = Date.now();
3124
- if (nowMs - (this.lastClientHeartbeat.get(input.clientId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
3125
- this.lastClientHeartbeat.set(input.clientId, nowMs);
3126
- this.pruneHeartbeats(this.lastClientHeartbeat, nowMs);
3127
- this.pruneClientsInPlace();
3128
- const client = this.readClients().get(input.clientId);
3129
- if (client !== void 0) {
3130
- client.lastSeenAt = new Date(nowMs).toISOString();
3131
- if (input.sessionId) client.sessionId = input.sessionId;
3132
- this.persistClient(client);
3133
- }
3134
- this.events?.emitCustom("mailbox.client_heartbeat", {
3135
- clientId: input.clientId,
3136
- ...input.sessionId ? { sessionId: input.sessionId } : {}
3137
- });
3138
- }
3139
- async getClientStatuses() {
3140
- this.pruneClientsInPlace();
3141
- return mapRegisteredClientsToStatuses(this.readClients(), Date.now(), CLIENT_STALE_MS);
3142
- }
3143
- async purgeClients() {
3144
- return this.pruneClientsInPlace();
3145
- }
3146
- async clearAll() {
3147
- this.stmt("DELETE FROM messages").run();
3148
- }
3149
- async purgeStale(options) {
3150
- return purgeStale(this.compactionCtx(), options);
3151
- }
3152
- async autoCompact(options) {
3153
- if (this.autoCompactInFlight !== void 0) return this.autoCompactInFlight;
3154
- const inFlight = autoCompact(this.compactionCtx(), options);
3155
- this.autoCompactInFlight = inFlight;
3156
- try {
3157
- return await inFlight;
3158
- } finally {
3159
- if (this.autoCompactInFlight === inFlight) this.autoCompactInFlight = void 0;
3160
- }
3161
- }
3162
- /** Bundle of store operations the retention sweeps drive. */
3163
- compactionCtx() {
3164
- return {
3165
- getAgentStatuses: () => this.getAgentStatuses(),
3166
- readMessages: () => this.readMessages(),
3167
- deleteMessages: (ids) => this.deleteMessages(ids)
3168
- };
3169
- }
3170
- deleteMessages(ids) {
3171
- if (ids.length === 0) return;
3172
- this.transaction(() => deleteMessages(this.db, ids));
3173
- }
3174
- credentialGet(credentialId) {
3175
- return credentialGet(this.db, credentialId);
3176
- }
3177
- credentialList() {
3178
- return credentialList(this.db);
3179
- }
3180
- credentialStatusCounts() {
3181
- return credentialStatusCounts(this.db);
3182
- }
3183
- credentialIssue(options) {
3184
- return credentialIssue(this.db, (run) => this.transaction(run), options);
3185
- }
3186
- credentialVerify(credentialId, secret) {
3187
- return credentialVerify(this.db, credentialId, secret);
3188
- }
3189
- credentialRevoke(credentialId, reason, by) {
3190
- return credentialRevoke(this.db, credentialId, reason, by);
3191
- }
3192
- credentialRotate(credentialId, options) {
3193
- return credentialRotate(this.db, (run) => this.transaction(run), credentialId, options);
3194
- }
3195
- startAutoCompactTimer(options) {
3196
- if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
3197
- const timer = setInterval(() => {
3198
- void this.autoCompact(options).catch(() => {
3199
- });
3200
- }, options?.intervalMs ?? AUTO_COMPACT_INTERVAL_MS);
3201
- timer.unref?.();
3202
- this.autoCompactTimer = timer;
3203
- return () => {
3204
- clearInterval(timer);
3205
- if (this.autoCompactTimer === timer) this.autoCompactTimer = null;
3206
- };
3207
- }
3208
- async close() {
3209
- if (this.closed) return;
3210
- this.closed = true;
3211
- if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
3212
- this.autoCompactTimer = null;
3213
- this.db.close();
3214
- }
3215
- };
3216
-
3217
- // src/coordination/mailbox-project-server.ts
45
+ import { bindProjectEndpoint } from "@wrongstack/persistence";
3218
46
  var DEFAULT_IDLE_MS = 5 * 6e4;
3219
47
  var DEFAULT_CLIENT_LEASE_MS = 45e3;
3220
48
  var MAX_CLIENT_WRITE_BUFFER_BYTES = 8 * 1024 * 1024;
@@ -3224,7 +52,7 @@ function parseArgs(argv) {
3224
52
  if (argv[index] === "--project-dir") projectDir2 = argv[++index];
3225
53
  }
3226
54
  if (!projectDir2) throw new Error("mailbox project server requires --project-dir");
3227
- return { projectDir: path7.resolve(projectDir2) };
55
+ return { projectDir: path.resolve(projectDir2) };
3228
56
  }
3229
57
  useDaemonPerfDefaults();
3230
58
  var { projectDir } = parseArgs(process.argv.slice(2));
@@ -3251,10 +79,10 @@ var stopMemoryWatchdog = startSharedHeapWatchdog({
3251
79
  pendingRequests
3252
80
  })
3253
81
  });
3254
- var authToken = randomBytes2(16).toString("hex");
82
+ var authToken = randomBytes(16).toString("hex");
3255
83
  var markMetadataWritten;
3256
- var metadataWritten = new Promise((resolve5) => {
3257
- markMetadataWritten = resolve5;
84
+ var metadataWritten = new Promise((resolve2) => {
85
+ markMetadataWritten = resolve2;
3258
86
  });
3259
87
  var serverInfo = {
3260
88
  protocolVersion: MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
@@ -3285,7 +113,7 @@ events.onAny((event, payload) => {
3285
113
  });
3286
114
  eventEmitter.subscribe((event) => broadcast({ type: "mailbox-event", event }));
3287
115
  function serverStatus() {
3288
- const databasePath = mailbox?.databasePath ?? path7.join(projectDir, SQLITE_MAILBOX_FILE);
116
+ const databasePath = mailbox?.databasePath ?? path.join(projectDir, SQLITE_MAILBOX_FILE);
3289
117
  return {
3290
118
  ...serverInfo,
3291
119
  clients: clients.size,
@@ -3473,9 +301,9 @@ function handleMessage(state, message) {
3473
301
  scheduleIdleStop();
3474
302
  });
3475
303
  }
3476
- function onData(state, chunk2) {
304
+ function onData(state, chunk) {
3477
305
  state.lastSeenAt = Date.now();
3478
- state.buffer += chunk2;
306
+ state.buffer += chunk;
3479
307
  while (true) {
3480
308
  const newline = state.buffer.indexOf("\n");
3481
309
  if (newline < 0) {
@@ -3539,7 +367,7 @@ async function stop(_reason) {
3539
367
  clearInterval(leaseSweep);
3540
368
  stopAutoCompact?.();
3541
369
  await removeOwnedMetadata();
3542
- const serverClosed = new Promise((resolve5) => server.close(() => resolve5()));
370
+ const serverClosed = new Promise((resolve2) => server.close(() => resolve2()));
3543
371
  for (const state of clients) state.socket.end();
3544
372
  const forceCloseTimer = setTimeout(() => {
3545
373
  for (const state of clients) state.socket.destroy();
@@ -3571,7 +399,7 @@ var server = net.createServer((socket) => {
3571
399
  void metadataWritten.then(() => {
3572
400
  if (!socket.destroyed) send(state, { type: "hello", ...serverInfo });
3573
401
  });
3574
- socket.on("data", (chunk2) => onData(state, chunk2));
402
+ socket.on("data", (chunk) => onData(state, chunk));
3575
403
  socket.on("error", () => {
3576
404
  });
3577
405
  socket.on("close", () => {