@wrongstack/core 0.313.0 → 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 (182) 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 -34324
  131. package/dist/coordination/mailbox-project-server.js +48 -3276
  132. package/dist/coordination/provider-status-tracker.d.ts +4 -0
  133. package/dist/core/agent.d.ts +14 -1
  134. package/dist/core/index.js +267 -12480
  135. package/dist/core/model-ref.js +5 -23
  136. package/dist/core/provider-runner.d.ts +36 -1
  137. package/dist/design/index.js +50 -1087
  138. package/dist/execution/index.d.ts +1 -1
  139. package/dist/execution/index.js +1599 -21888
  140. package/dist/execution/prompt-enhancer.js +16 -535
  141. package/dist/execution/retry-policy.d.ts +31 -1
  142. package/dist/extension/index.js +7 -311
  143. package/dist/goal/index.js +33 -3435
  144. package/dist/hooks/index.js +14 -1138
  145. package/dist/hq/index.js +112 -5139
  146. package/dist/hq/protocol.js +21 -668
  147. package/dist/index.js +1410 -92656
  148. package/dist/infrastructure/index.js +64 -4811
  149. package/dist/kernel/events/provider-events.d.ts +15 -0
  150. package/dist/kernel/index.js +14 -992
  151. package/dist/models/index.js +40 -1117
  152. package/dist/notifications/index.js +3 -108
  153. package/dist/observability/index.js +17 -279
  154. package/dist/plugin/index.js +584 -14835
  155. package/dist/prompts/index.js +14 -948
  156. package/dist/registry/index.js +9 -822
  157. package/dist/registry-R23UBE7H.js +14 -0
  158. package/dist/replay/index.js +6 -84
  159. package/dist/review-finding-store-DKAV3AS6.js +21 -0
  160. package/dist/review-report-store-OWY3EC3N.js +19 -0
  161. package/dist/security/index.js +114 -2202
  162. package/dist/session-catalog/index.js +36 -2103
  163. package/dist/session-catalog/project-server.js +42 -1435
  164. package/dist/skills/index.js +35 -1578
  165. package/dist/storage/index.js +182 -15600
  166. package/dist/tasking/index.js +10 -676
  167. package/dist/tools/index.js +52 -9590
  168. package/dist/types/config/autonomy.d.ts +5 -0
  169. package/dist/types/config/ui.d.ts +1 -1
  170. package/dist/types/errors.d.ts +18 -0
  171. package/dist/types/index.js +121 -1342
  172. package/dist/types/provider-runner.d.ts +8 -0
  173. package/dist/types/session-markers.js +8 -144
  174. package/dist/utils/error.js +3 -4
  175. package/dist/utils/expect-defined.js +3 -9
  176. package/dist/utils/heap-watchdog.js +6 -706
  177. package/dist/utils/index.js +269 -5685
  178. package/dist/utils/sage-output-block.js +5 -42
  179. package/dist/wiring/proxy-rewrite.d.ts +18 -0
  180. package/dist/wiring/proxy-rewrite.js +18 -121
  181. package/dist/worktree/index.js +6 -887
  182. package/package.json +4 -4
@@ -0,0 +1,1178 @@
1
+ import {
2
+ MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS,
3
+ MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
4
+ SQLITE_MAILBOX_FILE,
5
+ encodeMailboxProjectServerMessage,
6
+ isMailboxProjectServerMessage,
7
+ mailboxProjectServerEndpoint,
8
+ mailboxProjectServerMetadataPath
9
+ } from "./chunk-5RP3QTRL.js";
10
+ import {
11
+ HQ_MAILBOX_SNAPSHOT_MIN_INTERVAL_MS,
12
+ resolveProjectDir,
13
+ resolveSendTypeSafe
14
+ } from "./chunk-GM54GJBU.js";
15
+ import {
16
+ acceptMailboxMessageForSession,
17
+ isMailboxMessageVisibleTo,
18
+ normalizeRecipient
19
+ } from "./chunk-W4A6QA3D.js";
20
+ import {
21
+ ToolCapabilities
22
+ } from "./chunk-A455ATZB.js";
23
+ import {
24
+ wstackGlobalRoot
25
+ } from "./chunk-YNCVSFGF.js";
26
+ import {
27
+ toErrorMessage
28
+ } from "./chunk-TBRL2RCC.js";
29
+
30
+ // src/coordination/mailbox-project-server-client.ts
31
+ import { spawn } from "node:child_process";
32
+ import * as fs from "node:fs";
33
+ import * as net from "node:net";
34
+ import * as path from "node:path";
35
+ import { fileURLToPath } from "node:url";
36
+ var CONNECT_ATTEMPT_TIMEOUT_MS = 750;
37
+ var SERVER_START_TIMEOUT_MS = 1e4;
38
+ var DEFAULT_CALL_TIMEOUT_MS = 3e4;
39
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 1e4;
40
+ var AUTH_RETRY_DELAY_MS = 150;
41
+ var AUTH_RETRY_MAX_ATTEMPTS = 13;
42
+ function normalizePath(value) {
43
+ const resolved = path.resolve(value);
44
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
45
+ }
46
+ function resolveProjectServerUrl() {
47
+ for (const relative of [
48
+ // Unbundled / per-subpath output: dist/coordination/index.js.
49
+ "./mailbox-project-server.js",
50
+ // Root-level output — dist/index.js and, since core is built with code
51
+ // splitting, the shared chunks that sit next to it. Without this entry the
52
+ // lookup silently reports missing-build and every process quietly opens
53
+ // its own store, which is exactly the "one mailbox per project" invariant
54
+ // this resolver exists to protect.
55
+ "./coordination/mailbox-project-server.js",
56
+ "../../dist/coordination/mailbox-project-server.js"
57
+ ]) {
58
+ try {
59
+ const url = new URL(relative, import.meta.url);
60
+ if (url.protocol === "file:" && fs.existsSync(fileURLToPath(url))) return url;
61
+ } catch {
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+ function isMailboxProjectServerAvailable() {
67
+ return resolveProjectServerUrl() !== null;
68
+ }
69
+ function delay(ms) {
70
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
71
+ }
72
+ function isUnauthorizedMailboxError(error) {
73
+ return error instanceof Error && error.name === "UnauthorizedMailboxRequest";
74
+ }
75
+ var MailboxProjectServerConnection = class {
76
+ constructor(projectDir) {
77
+ this.projectDir = projectDir;
78
+ this.projectDir = path.resolve(projectDir);
79
+ this.endpoint = mailboxProjectServerEndpoint(this.projectDir);
80
+ this.state = {
81
+ status: isMailboxProjectServerAvailable() ? "offline" : "unavailable",
82
+ connected: false,
83
+ projectDir: this.projectDir,
84
+ endpoint: this.endpoint
85
+ };
86
+ }
87
+ projectDir;
88
+ endpoint;
89
+ socket = null;
90
+ info = null;
91
+ buffer = "";
92
+ connecting = null;
93
+ connectResolve = null;
94
+ connectReject = null;
95
+ nextId = 1;
96
+ /**
97
+ * WS-027: read from the daemon's owner-only metadata file, never from the
98
+ * `hello` frame — the daemon sends `hello` to every socket that connects, so
99
+ * a token carried there would be handed to exactly the caller it must
100
+ * refuse (the mistake WS-028 found in the SAGE daemon).
101
+ */
102
+ authToken;
103
+ heartbeatTimer = null;
104
+ pending = /* @__PURE__ */ new Map();
105
+ eventListeners = /* @__PURE__ */ new Set();
106
+ mailboxEventListeners = /* @__PURE__ */ new Set();
107
+ stateListeners = /* @__PURE__ */ new Set();
108
+ state;
109
+ getState() {
110
+ return { ...this.state };
111
+ }
112
+ onStateChange(listener) {
113
+ this.stateListeners.add(listener);
114
+ return () => this.stateListeners.delete(listener);
115
+ }
116
+ onEvent(listener) {
117
+ this.eventListeners.add(listener);
118
+ return () => this.eventListeners.delete(listener);
119
+ }
120
+ onMailboxEvent(listener) {
121
+ this.mailboxEventListeners.add(listener);
122
+ return () => this.mailboxEventListeners.delete(listener);
123
+ }
124
+ async connect() {
125
+ await this.ensureConnected(true);
126
+ }
127
+ async status() {
128
+ return this.call("ping", {}, { timeoutMs: 3e3 });
129
+ }
130
+ /**
131
+ * Inspect an existing project server without starting one.
132
+ * Returns null when no server is running (ECONNREFUSED / timeout).
133
+ * Mirrors the SAGE `status()` probe pattern.
134
+ */
135
+ async probeStatus() {
136
+ try {
137
+ await this.ensureConnected(false);
138
+ return await this.request(
139
+ { type: "request", op: "ping", args: {} },
140
+ 3e3
141
+ );
142
+ } catch {
143
+ this.close();
144
+ return null;
145
+ }
146
+ }
147
+ async call(op, args, options = {}) {
148
+ const timeoutMs = options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
149
+ let lastError;
150
+ for (let attempt = 0; attempt <= AUTH_RETRY_MAX_ATTEMPTS; attempt++) {
151
+ try {
152
+ await this.ensureConnected(true);
153
+ return await this.request(
154
+ { type: "request", op, args },
155
+ timeoutMs
156
+ );
157
+ } catch (error) {
158
+ lastError = error;
159
+ const retriable = isUnauthorizedMailboxError(error) && attempt < AUTH_RETRY_MAX_ATTEMPTS;
160
+ if (!retriable) throw error;
161
+ await delay(AUTH_RETRY_DELAY_MS);
162
+ }
163
+ }
164
+ throw lastError;
165
+ }
166
+ async shutdown(reason) {
167
+ try {
168
+ await this.ensureConnected(false);
169
+ } catch {
170
+ return { stopped: false, reason: "offline" };
171
+ }
172
+ const pid = this.info?.pid;
173
+ const result = await this.request(
174
+ { type: "shutdown", reason },
175
+ 5e3
176
+ );
177
+ return { ...result, ...pid !== void 0 && result.pid === void 0 ? { pid } : {} };
178
+ }
179
+ close() {
180
+ this.stopHeartbeat();
181
+ const socket = this.socket;
182
+ this.socket = null;
183
+ this.info = null;
184
+ socket?.destroy();
185
+ this.rejectPending(new Error("Mailbox project server connection closed"));
186
+ this.transition("offline");
187
+ }
188
+ transition(status, error) {
189
+ this.state = {
190
+ status,
191
+ connected: status === "connected",
192
+ projectDir: this.projectDir,
193
+ endpoint: this.endpoint,
194
+ ...status === "connected" && this.info ? { pid: this.info.pid } : {},
195
+ ...error !== void 0 ? { lastError: error instanceof Error ? error.message : String(error) } : {}
196
+ };
197
+ for (const listener of [...this.stateListeners]) listener({ ...this.state });
198
+ }
199
+ async ensureConnected(spawnIfMissing) {
200
+ if (this.socket && !this.socket.destroyed && this.info) return;
201
+ if (this.connecting) return this.connecting;
202
+ if (!isMailboxProjectServerAvailable()) {
203
+ this.transition("unavailable");
204
+ throw new Error(
205
+ "Built mailbox project server is unavailable. Run `pnpm --filter @wrongstack/core build`. The mailbox has no in-process mode: there is no env var to fall back to, by design."
206
+ );
207
+ }
208
+ this.transition("connecting");
209
+ this.connecting = this.connectWithElection(spawnIfMissing).catch((error) => {
210
+ this.transition("error", error);
211
+ throw error;
212
+ }).finally(() => {
213
+ this.connecting = null;
214
+ });
215
+ return this.connecting;
216
+ }
217
+ async connectWithElection(spawnIfMissing) {
218
+ const deadline = Date.now() + (spawnIfMissing ? SERVER_START_TIMEOUT_MS : CONNECT_ATTEMPT_TIMEOUT_MS);
219
+ let spawned = false;
220
+ let lastError = new Error("Mailbox project server unavailable");
221
+ while (Date.now() < deadline) {
222
+ try {
223
+ await this.connectOnce();
224
+ return;
225
+ } catch (error) {
226
+ lastError = error;
227
+ }
228
+ if (!spawnIfMissing) break;
229
+ if (!spawned) {
230
+ this.spawnDetachedServer();
231
+ spawned = true;
232
+ }
233
+ await delay(75);
234
+ }
235
+ throw lastError;
236
+ }
237
+ connectOnce() {
238
+ this.socket?.destroy();
239
+ this.socket = null;
240
+ this.info = null;
241
+ this.buffer = "";
242
+ this.authToken = void 0;
243
+ return new Promise((resolve3, reject) => {
244
+ const socket = net.createConnection(this.endpoint);
245
+ this.socket = socket;
246
+ socket.setEncoding("utf8");
247
+ const timer = setTimeout(() => {
248
+ this.connectReject?.(new Error("Mailbox project server handshake timed out"));
249
+ socket.destroy();
250
+ }, CONNECT_ATTEMPT_TIMEOUT_MS);
251
+ timer.unref?.();
252
+ this.connectResolve = () => {
253
+ clearTimeout(timer);
254
+ this.connectResolve = null;
255
+ this.connectReject = null;
256
+ this.startHeartbeat();
257
+ this.transition("connected");
258
+ resolve3();
259
+ void this.request({ type: "request", op: "ping", args: {} }, 3e3).catch(
260
+ () => void 0
261
+ );
262
+ };
263
+ this.connectReject = (error) => {
264
+ clearTimeout(timer);
265
+ this.connectResolve = null;
266
+ this.connectReject = null;
267
+ reject(error);
268
+ };
269
+ socket.on("data", (chunk) => this.onData(socket, chunk));
270
+ socket.on("error", (error) => {
271
+ if (!this.info) this.connectReject?.(error);
272
+ });
273
+ socket.on("close", () => this.onClose(socket));
274
+ });
275
+ }
276
+ /**
277
+ * The token, read lazily and re-read while still unknown.
278
+ *
279
+ * The daemon starts listening before it writes its metadata (endpoint
280
+ * ownership has to be won first, and clobbering another daemon's file to win
281
+ * it would be worse), so a client that connects in that window sees no file
282
+ * yet. Re-reading until one is found costs a single small `readFileSync`.
283
+ */
284
+ currentAuthToken() {
285
+ if (this.authToken === void 0) {
286
+ try {
287
+ const raw = fs.readFileSync(mailboxProjectServerMetadataPath(this.projectDir), "utf8");
288
+ const parsed = JSON.parse(raw);
289
+ if (typeof parsed.authToken === "string" && parsed.authToken.length > 0) {
290
+ this.authToken = parsed.authToken;
291
+ }
292
+ } catch {
293
+ }
294
+ }
295
+ return this.authToken;
296
+ }
297
+ request(message, timeoutMs) {
298
+ const socket = this.socket;
299
+ if (!socket || socket.destroyed) {
300
+ return Promise.reject(new Error("Mailbox project server connection is unavailable"));
301
+ }
302
+ const id = this.nextId++;
303
+ const encoded = encodeMailboxProjectServerMessage({
304
+ ...message,
305
+ id,
306
+ authToken: this.currentAuthToken()
307
+ });
308
+ if (encoded.length > MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS) {
309
+ return Promise.reject(new Error("Mailbox project server request exceeded frame limit"));
310
+ }
311
+ return new Promise((resolve3, reject) => {
312
+ const timer = setTimeout(() => {
313
+ const pending = this.pending.get(id);
314
+ if (!pending) return;
315
+ this.pending.delete(id);
316
+ pending.reject(new Error(`Mailbox ${message.type} exceeded its ${timeoutMs}ms timeout`));
317
+ }, timeoutMs);
318
+ timer.unref?.();
319
+ this.pending.set(id, { resolve: resolve3, reject, timer });
320
+ socket.write(encoded);
321
+ });
322
+ }
323
+ onData(socket, chunk) {
324
+ if (socket !== this.socket) return;
325
+ this.buffer += chunk;
326
+ while (true) {
327
+ const newline = this.buffer.indexOf("\n");
328
+ if (newline < 0) {
329
+ if (this.buffer.length > MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS) {
330
+ socket.destroy(new Error("Mailbox project server response exceeded frame limit"));
331
+ }
332
+ return;
333
+ }
334
+ if (newline > MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS) {
335
+ socket.destroy(new Error("Mailbox project server response exceeded frame limit"));
336
+ return;
337
+ }
338
+ const line = this.buffer.slice(0, newline);
339
+ this.buffer = this.buffer.slice(newline + 1);
340
+ if (!line) continue;
341
+ let parsed;
342
+ try {
343
+ parsed = JSON.parse(line);
344
+ } catch {
345
+ socket.destroy(new Error("Invalid mailbox project server response"));
346
+ return;
347
+ }
348
+ if (!isMailboxProjectServerMessage(parsed)) {
349
+ socket.destroy(new Error("Invalid mailbox project server response"));
350
+ return;
351
+ }
352
+ this.onMessage(parsed);
353
+ }
354
+ }
355
+ onMessage(message) {
356
+ if (message.type === "hello") {
357
+ if (message.protocolVersion !== MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION) {
358
+ this.connectReject?.(
359
+ new Error(
360
+ `Mailbox protocol mismatch: client=${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}, server=${message.protocolVersion}`
361
+ )
362
+ );
363
+ this.socket?.destroy();
364
+ return;
365
+ }
366
+ if (normalizePath(message.projectDir) !== normalizePath(this.projectDir)) {
367
+ this.connectReject?.(new Error("Mailbox project server identity mismatch"));
368
+ this.socket?.destroy();
369
+ return;
370
+ }
371
+ this.info = message;
372
+ this.connectResolve?.();
373
+ return;
374
+ }
375
+ if (message.type === "event") {
376
+ for (const listener of [...this.eventListeners]) listener(message.event, message.payload);
377
+ return;
378
+ }
379
+ if (message.type === "mailbox-event") {
380
+ for (const listener of [...this.mailboxEventListeners]) listener(message.event);
381
+ return;
382
+ }
383
+ const pending = this.pending.get(message.id);
384
+ if (!pending) return;
385
+ this.pending.delete(message.id);
386
+ clearTimeout(pending.timer);
387
+ if (message.ok) pending.resolve(message.result);
388
+ else {
389
+ if (message.errorName === "UnauthorizedMailboxRequest") this.authToken = void 0;
390
+ const error = new Error(message.error);
391
+ if (message.errorName) error.name = message.errorName;
392
+ pending.reject(error);
393
+ }
394
+ }
395
+ onClose(socket) {
396
+ if (socket !== this.socket) return;
397
+ this.socket = null;
398
+ this.info = null;
399
+ this.stopHeartbeat();
400
+ const error = new Error("Mailbox project server connection closed");
401
+ this.connectReject?.(error);
402
+ this.connectResolve = null;
403
+ this.connectReject = null;
404
+ this.rejectPending(error);
405
+ this.transition("offline");
406
+ }
407
+ rejectPending(error) {
408
+ const pending = [...this.pending.values()];
409
+ this.pending.clear();
410
+ for (const item of pending) {
411
+ clearTimeout(item.timer);
412
+ item.reject(error);
413
+ }
414
+ }
415
+ startHeartbeat() {
416
+ this.stopHeartbeat();
417
+ const configured = Number(process.env["WRONGSTACK_MAILBOX_CLIENT_HEARTBEAT_MS"]);
418
+ const intervalMs = Number.isFinite(configured) && configured >= 50 ? configured : DEFAULT_HEARTBEAT_INTERVAL_MS;
419
+ this.heartbeatTimer = setInterval(() => {
420
+ const socket = this.socket;
421
+ if (socket && !socket.destroyed) {
422
+ socket.write(encodeMailboxProjectServerMessage({ type: "heartbeat" }));
423
+ }
424
+ }, intervalMs);
425
+ this.heartbeatTimer.unref?.();
426
+ }
427
+ stopHeartbeat() {
428
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
429
+ this.heartbeatTimer = null;
430
+ }
431
+ spawnDetachedServer() {
432
+ const url = resolveProjectServerUrl();
433
+ if (!url) throw new Error("Mailbox project server entrypoint is unavailable");
434
+ fs.mkdirSync(this.projectDir, { recursive: true });
435
+ const child = spawn(process.execPath, [fileURLToPath(url), "--project-dir", this.projectDir], {
436
+ cwd: this.projectDir,
437
+ detached: process.platform !== "win32",
438
+ stdio: "ignore",
439
+ windowsHide: true,
440
+ env: process.env
441
+ });
442
+ child.unref();
443
+ }
444
+ };
445
+
446
+ // src/coordination/remote-mailbox.ts
447
+ import * as path2 from "node:path";
448
+ var HQ_MAILBOX_EVENT_MAX_PENDING = 256;
449
+ var sharedRemoteMailboxes = /* @__PURE__ */ new Map();
450
+ var sharedProjectConnections = /* @__PURE__ */ new Map();
451
+ function dependencyCache() {
452
+ return { byPublisher: /* @__PURE__ */ new WeakMap() };
453
+ }
454
+ var RemoteMailbox = class {
455
+ projectDir;
456
+ messagePath;
457
+ registryPath;
458
+ clientRegistryPath;
459
+ connection;
460
+ ownsConnection;
461
+ sharedConnectionKey;
462
+ events;
463
+ hqPublisherRef;
464
+ eventEmitter;
465
+ unsubscribeEvent;
466
+ unsubscribeMailboxEvent;
467
+ cacheEviction;
468
+ closed = false;
469
+ /** Pending coalesced HQ snapshot; see {@link scheduleHqSnapshot}. */
470
+ hqSnapshotTimer;
471
+ hqSnapshotLastAt = 0;
472
+ /** At most one full snapshot may query/map/serialize mailbox state at a time. */
473
+ hqSnapshotInFlight;
474
+ /** Latest scope requested while a timer or snapshot is already active. */
475
+ hqSnapshotPendingMailboxId;
476
+ /** Message deltas are serialized so a burst cannot launch one full mailbox
477
+ * query per event concurrently. Newer updates for the same message replace
478
+ * older pending ones; the periodic snapshot remains the lossless rollup. */
479
+ hqEventPending = /* @__PURE__ */ new Map();
480
+ hqEventInFlight;
481
+ hqEventCoalesced = 0;
482
+ hqEventDropped = 0;
483
+ constructor(optionsOrProjectDir, events, hqPublisher, eventEmitter) {
484
+ const options = typeof optionsOrProjectDir === "string" ? {
485
+ projectDir: optionsOrProjectDir,
486
+ events,
487
+ hqPublisher,
488
+ eventEmitter
489
+ } : optionsOrProjectDir;
490
+ this.projectDir = path2.resolve(options.projectDir);
491
+ this.messagePath = path2.join(this.projectDir, SQLITE_MAILBOX_FILE);
492
+ this.registryPath = this.messagePath;
493
+ this.clientRegistryPath = this.messagePath;
494
+ this.events = options.events;
495
+ this.hqPublisherRef = options.hqPublisher;
496
+ this.eventEmitter = options.eventEmitter;
497
+ if (options.isolatedConnection) {
498
+ this.connection = new MailboxProjectServerConnection(this.projectDir);
499
+ this.ownsConnection = true;
500
+ } else {
501
+ let entry = sharedProjectConnections.get(this.projectDir);
502
+ if (!entry) {
503
+ entry = {
504
+ connection: new MailboxProjectServerConnection(this.projectDir),
505
+ references: 0
506
+ };
507
+ sharedProjectConnections.set(this.projectDir, entry);
508
+ }
509
+ entry.references++;
510
+ this.connection = entry.connection;
511
+ this.sharedConnectionKey = this.projectDir;
512
+ this.ownsConnection = false;
513
+ }
514
+ this.unsubscribeEvent = this.connection.onEvent((event, payload) => {
515
+ this.events?.emitCustom(event, payload);
516
+ this.publishHqRegistryEvent(event, payload);
517
+ });
518
+ this.unsubscribeMailboxEvent = this.connection.onMailboxEvent((event) => {
519
+ this.eventEmitter?.emit(event);
520
+ this.publishHqEvent(event);
521
+ });
522
+ void this.connection.connect().catch(() => {
523
+ });
524
+ }
525
+ getConnectionState() {
526
+ return this.connection.getState();
527
+ }
528
+ onConnectionStateChange(listener) {
529
+ return this.connection.onStateChange(listener);
530
+ }
531
+ async initialize() {
532
+ await this.connection.connect();
533
+ await this.connection.status();
534
+ }
535
+ async status() {
536
+ return this.connection.status();
537
+ }
538
+ send(input) {
539
+ return this.connection.call("send", { input });
540
+ }
541
+ sendRuntimeControl(input) {
542
+ return this.connection.call("sendRuntimeControl", { input });
543
+ }
544
+ query(query) {
545
+ return this.connection.call("query", { query });
546
+ }
547
+ ack(input) {
548
+ return this.connection.call("ack", { input });
549
+ }
550
+ ackMany(input) {
551
+ return this.connection.call("ackMany", { input });
552
+ }
553
+ unreadCount(forAgentId, sessionId) {
554
+ return this.connection.call("unreadCount", { forAgentId, sessionId });
555
+ }
556
+ softDelete(mailId, by) {
557
+ return this.connection.call("softDelete", { mailId, by });
558
+ }
559
+ restore(mailId) {
560
+ return this.connection.call("restore", { mailId });
561
+ }
562
+ registerAgent(input) {
563
+ return this.connection.call("registerAgent", { input });
564
+ }
565
+ deregisterAgent(agentId) {
566
+ return this.connection.call("deregisterAgent", { agentId });
567
+ }
568
+ heartbeat(input) {
569
+ return this.connection.call("heartbeat", { input });
570
+ }
571
+ getAgentStatuses() {
572
+ return this.connection.call("getAgentStatuses", {});
573
+ }
574
+ getOnlineAgents() {
575
+ return this.connection.call("getOnlineAgents", {});
576
+ }
577
+ purgeAgents(maxAgeMs) {
578
+ return this.connection.call("purgeAgents", { maxAgeMs });
579
+ }
580
+ registerClient(input) {
581
+ return this.connection.call("registerClient", { input });
582
+ }
583
+ deregisterClient(clientId) {
584
+ return this.connection.call("deregisterClient", { clientId });
585
+ }
586
+ clientHeartbeat(input) {
587
+ return this.connection.call("clientHeartbeat", { input });
588
+ }
589
+ getClientStatuses() {
590
+ return this.connection.call("getClientStatuses", {});
591
+ }
592
+ purgeClients() {
593
+ return this.connection.call("purgeClients", {});
594
+ }
595
+ clearAll() {
596
+ return this.connection.call("clearAll", {});
597
+ }
598
+ purgeStale(options) {
599
+ return this.connection.call("purgeStale", { options });
600
+ }
601
+ autoCompact(options) {
602
+ return this.connection.call("autoCompact", { options }, { timeoutMs: 2 * 6e4 });
603
+ }
604
+ credentialIssue(options) {
605
+ return this.connection.call("credentialIssue", { options });
606
+ }
607
+ credentialVerify(credentialId, secret) {
608
+ return this.connection.call("credentialVerify", { credentialId, secret });
609
+ }
610
+ credentialRevoke(credentialId, reason, by) {
611
+ return this.connection.call("credentialRevoke", { credentialId, reason, by });
612
+ }
613
+ credentialRotate(credentialId, options) {
614
+ return this.connection.call("credentialRotate", { credentialId, options });
615
+ }
616
+ credentialGet(credentialId) {
617
+ return this.connection.call("credentialGet", { credentialId });
618
+ }
619
+ credentialList() {
620
+ return this.connection.call("credentialList", {});
621
+ }
622
+ credentialStatusCounts() {
623
+ return this.connection.call("credentialStatusCounts", {});
624
+ }
625
+ startAutoCompactTimer(_options) {
626
+ return () => {
627
+ };
628
+ }
629
+ async close() {
630
+ if (this.closed) return;
631
+ this.closed = true;
632
+ if (this.hqSnapshotTimer !== void 0) {
633
+ clearTimeout(this.hqSnapshotTimer);
634
+ this.hqSnapshotTimer = void 0;
635
+ }
636
+ this.hqSnapshotPendingMailboxId = void 0;
637
+ this.hqEventPending.clear();
638
+ this.cacheEviction?.();
639
+ this.cacheEviction = void 0;
640
+ this.unsubscribeEvent();
641
+ this.unsubscribeMailboxEvent();
642
+ if (this.ownsConnection) {
643
+ this.connection.close();
644
+ return;
645
+ }
646
+ if (this.sharedConnectionKey !== void 0) {
647
+ const entry = sharedProjectConnections.get(this.sharedConnectionKey);
648
+ if (entry?.connection === this.connection) {
649
+ entry.references = Math.max(0, entry.references - 1);
650
+ if (entry.references === 0) {
651
+ entry.connection.close();
652
+ sharedProjectConnections.delete(this.sharedConnectionKey);
653
+ }
654
+ }
655
+ }
656
+ }
657
+ /** @internal Remove a process-shared wrapper from its cache when explicitly closed. */
658
+ setCacheEviction(evict) {
659
+ this.cacheEviction = evict;
660
+ }
661
+ /** Lightweight diagnostics for the process memory flight recorder. */
662
+ getHqSnapshotStats() {
663
+ return {
664
+ inFlight: this.hqSnapshotInFlight !== void 0,
665
+ pending: this.hqSnapshotPendingMailboxId !== void 0,
666
+ timerScheduled: this.hqSnapshotTimer !== void 0,
667
+ eventInFlight: this.hqEventInFlight !== void 0,
668
+ pendingEvents: this.hqEventPending.size,
669
+ coalescedEvents: this.hqEventCoalesced,
670
+ droppedEvents: this.hqEventDropped
671
+ };
672
+ }
673
+ get hqPublisher() {
674
+ return typeof this.hqPublisherRef === "function" ? this.hqPublisherRef() : this.hqPublisherRef;
675
+ }
676
+ /**
677
+ * Publish a full HQ mailbox snapshot, at most once per
678
+ * {@link HQ_MAILBOX_SNAPSHOT_MIN_INTERVAL_MS}.
679
+ *
680
+ * Trailing-edge: the first caller after the interval schedules one, and
681
+ * every caller in the window folds into it. The snapshot is a rollup of
682
+ * current state, so a coalesced publish carries strictly more up-to-date
683
+ * information than the ones it replaced — dropping them loses nothing.
684
+ */
685
+ scheduleHqSnapshot(mailboxId) {
686
+ if (this.closed) return;
687
+ this.hqSnapshotPendingMailboxId = mailboxId;
688
+ if (this.hqSnapshotTimer !== void 0 || this.hqSnapshotInFlight !== void 0) return;
689
+ const elapsed = Date.now() - this.hqSnapshotLastAt;
690
+ const delay2 = Math.max(0, HQ_MAILBOX_SNAPSHOT_MIN_INTERVAL_MS - elapsed);
691
+ this.hqSnapshotTimer = setTimeout(() => {
692
+ this.hqSnapshotTimer = void 0;
693
+ const pendingMailboxId = this.hqSnapshotPendingMailboxId;
694
+ this.hqSnapshotPendingMailboxId = void 0;
695
+ const publisher = this.hqPublisher;
696
+ if (this.closed || !publisher || pendingMailboxId === void 0) return;
697
+ this.hqSnapshotLastAt = Date.now();
698
+ const inFlight = (async () => {
699
+ try {
700
+ await publisher.publishMailboxSnapshot(this, { mailboxId: pendingMailboxId });
701
+ } catch {
702
+ } finally {
703
+ this.hqSnapshotInFlight = void 0;
704
+ const nextMailboxId = this.hqSnapshotPendingMailboxId;
705
+ if (!this.closed && nextMailboxId !== void 0) {
706
+ this.scheduleHqSnapshot(nextMailboxId);
707
+ }
708
+ }
709
+ })();
710
+ this.hqSnapshotInFlight = inFlight;
711
+ }, delay2);
712
+ this.hqSnapshotTimer.unref?.();
713
+ }
714
+ publishHqEvent(event) {
715
+ if (!this.hqPublisher || this.closed) return;
716
+ if (this.hqEventPending.has(event.messageId)) {
717
+ this.hqEventCoalesced += 1;
718
+ } else if (this.hqEventPending.size >= HQ_MAILBOX_EVENT_MAX_PENDING) {
719
+ const oldest = this.hqEventPending.keys().next().value;
720
+ if (oldest !== void 0) this.hqEventPending.delete(oldest);
721
+ this.hqEventDropped += 1;
722
+ }
723
+ this.hqEventPending.set(event.messageId, event);
724
+ this.drainHqEvents();
725
+ }
726
+ drainHqEvents() {
727
+ if (this.closed || this.hqEventInFlight !== void 0) return;
728
+ const next = this.hqEventPending.entries().next().value;
729
+ if (!next) return;
730
+ const [messageId, event] = next;
731
+ this.hqEventPending.delete(messageId);
732
+ const publisher = this.hqPublisher;
733
+ if (!publisher) {
734
+ this.hqEventPending.clear();
735
+ return;
736
+ }
737
+ const mailboxId = `${path2.basename(this.projectDir)}:mailbox`;
738
+ const inFlight = this.query({ includeDeleted: true, limit: 100 }).then((messages) => {
739
+ const message = messages.find((candidate) => candidate.id === event.messageId);
740
+ const action = event.type === "message.sent" ? "message.sent" : "message.updated";
741
+ publisher.publishMailboxEvent({
742
+ mailboxId,
743
+ action,
744
+ ...message ? { message } : {},
745
+ timestamp: event.timestamp
746
+ });
747
+ this.scheduleHqSnapshot(mailboxId);
748
+ }).catch(() => {
749
+ }).finally(() => {
750
+ this.hqEventInFlight = void 0;
751
+ this.drainHqEvents();
752
+ });
753
+ this.hqEventInFlight = inFlight;
754
+ }
755
+ publishHqRegistryEvent(event, payload) {
756
+ const publisher = this.hqPublisher;
757
+ if (!publisher || this.closed || !event.startsWith("mailbox.agent_") && !event.startsWith("mailbox.client_")) {
758
+ return;
759
+ }
760
+ const mailboxId = `${path2.basename(this.projectDir)}:mailbox`;
761
+ const record = typeof payload === "object" && payload !== null ? payload : {};
762
+ const agentId = typeof record["agentId"] === "string" ? record["agentId"] : void 0;
763
+ const action = event === "mailbox.agent_registered" ? "agent.registered" : event === "mailbox.agent_heartbeat" ? "agent.heartbeat" : event === "mailbox.agent_deregistered" ? "agent.deregistered" : void 0;
764
+ if (action !== "agent.registered") {
765
+ if (action) {
766
+ publisher.publishMailboxEvent({
767
+ mailboxId,
768
+ action,
769
+ ...agentId ? { summary: agentId } : {}
770
+ });
771
+ }
772
+ if (action !== "agent.heartbeat") this.scheduleHqSnapshot(mailboxId);
773
+ return;
774
+ }
775
+ void this.getAgentStatuses().then((statuses) => {
776
+ if (this.closed) return;
777
+ const agent = agentId ? statuses.find((candidate) => candidate.agentId === agentId) : void 0;
778
+ publisher.publishMailboxEvent({
779
+ mailboxId,
780
+ action,
781
+ ...agent ? { agent } : {},
782
+ ...agentId ? { summary: agentId } : {}
783
+ });
784
+ this.scheduleHqSnapshot(mailboxId);
785
+ }).catch(() => {
786
+ });
787
+ }
788
+ };
789
+ function createProjectMailbox(options) {
790
+ return new RemoteMailbox(options);
791
+ }
792
+ function getSharedProjectMailbox(projectDir, events, hqPublisher) {
793
+ const key = path2.resolve(projectDir);
794
+ let projectCache = sharedRemoteMailboxes.get(key);
795
+ if (!projectCache) {
796
+ projectCache = {
797
+ withoutEvents: dependencyCache(),
798
+ byEvents: /* @__PURE__ */ new WeakMap()
799
+ };
800
+ sharedRemoteMailboxes.set(key, projectCache);
801
+ }
802
+ let dependencies = projectCache.withoutEvents;
803
+ if (events) {
804
+ dependencies = projectCache.byEvents.get(events) ?? dependencyCache();
805
+ projectCache.byEvents.set(events, dependencies);
806
+ }
807
+ if (!hqPublisher) {
808
+ if (!dependencies.withoutPublisher) {
809
+ const mailbox2 = new RemoteMailbox(key, events);
810
+ dependencies.withoutPublisher = mailbox2;
811
+ mailbox2.setCacheEviction(() => {
812
+ if (dependencies.withoutPublisher === mailbox2) {
813
+ delete dependencies.withoutPublisher;
814
+ }
815
+ });
816
+ }
817
+ return dependencies.withoutPublisher;
818
+ }
819
+ const publisherKey = Object(hqPublisher);
820
+ const existing = dependencies.byPublisher.get(publisherKey);
821
+ if (existing) return existing;
822
+ const mailbox = new RemoteMailbox(key, events, hqPublisher);
823
+ dependencies.byPublisher.set(publisherKey, mailbox);
824
+ mailbox.setCacheEviction(() => {
825
+ if (dependencies.byPublisher.get(publisherKey) === mailbox) {
826
+ dependencies.byPublisher.delete(publisherKey);
827
+ }
828
+ });
829
+ return mailbox;
830
+ }
831
+
832
+ // src/coordination/mailbox-tool.ts
833
+ import { createHash } from "node:crypto";
834
+ function defaultResolveProjectDir(ctx) {
835
+ return resolveProjectDir(ctx.projectRoot, wstackGlobalRoot());
836
+ }
837
+ function mailboxSessionTag(sessionId) {
838
+ return createHash("sha256").update(sessionId).digest("hex").slice(0, 8);
839
+ }
840
+ function resolveMailboxIdentity(ctx, fallbackBase = "leader") {
841
+ const fieldId = ctx.agentId && ctx.agentId !== "unknown" ? ctx.agentId : void 0;
842
+ const baseId = ctx.meta["agentId"] ?? fieldId ?? fallbackBase;
843
+ const sessionId = ctx.meta["sessionId"] ?? ctx.session?.id ?? "default";
844
+ const callerId = ctx.meta["globalAgentId"] ?? `${baseId}@${mailboxSessionTag(sessionId)}`;
845
+ const fieldName = ctx.agentName && ctx.agentName !== "Unknown Agent" ? ctx.agentName : void 0;
846
+ const name = ctx.meta["agentName"] ?? fieldName ?? baseId;
847
+ const role = ctx.meta["agentRole"];
848
+ return { baseId, callerId, name, role, sessionId };
849
+ }
850
+ function applyMailboxSendPolicy(ctx, identity, requestedTo, requestedAudience) {
851
+ const policy = ctx.meta["mailboxSendPolicy"];
852
+ const isChimeraIdentity = (value) => {
853
+ const normalized = value.trim().toLowerCase();
854
+ return normalized === "chimera" || normalized.startsWith("chimera-");
855
+ };
856
+ const chimeraSender = isChimeraIdentity(identity.baseId) || isChimeraIdentity(identity.name);
857
+ if (policy === "leaders-only" || chimeraSender) {
858
+ return { to: "leader", audience: "leaders" };
859
+ }
860
+ return {
861
+ to: requestedTo,
862
+ ...requestedAudience !== void 0 ? { audience: requestedAudience } : {}
863
+ };
864
+ }
865
+ function makeMailboxTool(opts = {}) {
866
+ const resolveMailbox = opts.resolveMailbox ?? ((ctx) => {
867
+ const dir = opts.projectDir ?? defaultResolveProjectDir(ctx);
868
+ return getSharedProjectMailbox(dir, opts.events);
869
+ });
870
+ const agentId = opts.agentId ?? "leader";
871
+ const sessionId = opts.sessionId ?? "default";
872
+ const shortHint = "Sub-commands: check (unread), send (exact/base/@session/project broadcast), ack (read/complete), query (filter), status (all agents), online (active only), unread (count).";
873
+ return {
874
+ name: "mailbox",
875
+ description: 'Low-level inter-agent mailbox with 7 actions (check, send, ack, query, status, online, unread). For most use cases, prefer the simpler `mail_send` and `mail_inbox` tools \u2014 they cover the common send/read operations with a cleaner interface. This tool is for advanced queries (filter by sender, sender session, priority, since-timestamp), agent status/online lists, and message ack/control. Use to="@session" only for session-local coordination; use "*" or a base alias when another session may be affected.',
876
+ usageHint: shortHint + " For simple send/read, use mail_send / mail_inbox instead.",
877
+ category: "Coordination",
878
+ permission: "auto",
879
+ mutating: true,
880
+ capabilities: [ToolCapabilities.COORDINATION_MAIL],
881
+ inputSchema: {
882
+ type: "object",
883
+ properties: {
884
+ action: {
885
+ type: "string",
886
+ enum: ["check", "send", "ack", "query", "status", "online", "unread"],
887
+ description: "Which mailbox operation to perform."
888
+ },
889
+ to: { type: "string", description: "Recipient agent id, base alias, '@session' for the sender's session, or '*' / 'all' for project broadcast." },
890
+ type: { type: "string", enum: ["note", "ask", "assign", "steer", "btw", "broadcast", "status", "result", "review", "control"], description: "Required message intent. Actionable: ask (blocking question), assign (task delegation, must have specific to), steer (mid-course direction), review (passive ask). Informational: note (general FYI), btw (low-priority aside), result (completion notice), status (system update). Routing: broadcast (multi-recipient). Reserved: control (runtime only, agents cannot send)." },
891
+ subject: { type: "string", description: "Short subject line." },
892
+ body: { type: "string", description: "Full message content." },
893
+ priority: { type: "string", enum: ["low", "normal", "high"] },
894
+ audience: { type: "string", enum: ["all", "leaders"], description: 'Delivery audience. "leaders" prevents subagent consumption.' },
895
+ replyTo: { type: "string", description: "Reply to a specific message id." },
896
+ messageId: { type: "string", description: "Message id to acknowledge. Required for 'ack'." },
897
+ read: { type: "boolean", description: "Mark as read (adds read receipt)." },
898
+ markRead: { type: "boolean", description: "For action=check, add read receipts for returned messages (default true)." },
899
+ completed: { type: "boolean", description: "Mark as completed. For action=check, completes every returned message." },
900
+ outcome: { type: "string", description: "Outcome summary when marking complete." },
901
+ unreadBy: { type: "string", description: "Filter messages unread by this agent. Used by 'check'." },
902
+ incompleteOnly: { type: "boolean", description: "Only incomplete messages." },
903
+ from: { type: "string", description: "Filter by sender." },
904
+ minPriority: { type: "string", enum: ["low", "normal", "high"] },
905
+ since: { type: "string", description: "ISO8601 timestamp \u2014 only messages after this." },
906
+ sessionId: { type: "string", description: "Filter query results by sender session id." },
907
+ limit: { type: "number", description: "Max messages to return." }
908
+ },
909
+ required: ["action"]
910
+ },
911
+ async execute(input, ctx) {
912
+ const mb = resolveMailbox(ctx);
913
+ const i = input ?? {};
914
+ const action = i.action;
915
+ const identity = resolveMailboxIdentity(ctx, agentId);
916
+ const baseCallerId = identity.baseId;
917
+ const callerId = identity.callerId;
918
+ const callerSessionId = ctx.meta["sessionId"] ?? (ctx.session?.id ?? sessionId);
919
+ try {
920
+ await mb.registerAgent({
921
+ agentId: callerId,
922
+ sessionId: callerSessionId,
923
+ name: identity.name,
924
+ role: identity.role,
925
+ pid: process.pid,
926
+ source: ctx.meta["source"] ?? "cli"
927
+ });
928
+ } catch {
929
+ }
930
+ try {
931
+ await mb.heartbeat({ agentId: callerId });
932
+ } catch {
933
+ }
934
+ switch (action) {
935
+ case "check":
936
+ return executeCheck(mb, callerId, callerSessionId, [baseCallerId], identity.role, i);
937
+ case "send":
938
+ return executeSend(mb, identity, callerSessionId, i, ctx);
939
+ case "ack":
940
+ return executeAck(mb, callerId, i);
941
+ case "query":
942
+ return executeQuery(mb, callerId, callerSessionId, identity.role, i);
943
+ case "status":
944
+ return executeStatus(mb);
945
+ case "online":
946
+ return executeOnline(mb);
947
+ case "unread":
948
+ return executeUnread(mb, callerId, callerSessionId, [baseCallerId], identity.role);
949
+ default:
950
+ return { ok: false, error: `Unknown action: "${action}". Use check, send, ack, query, status, online, or unread.` };
951
+ }
952
+ }
953
+ };
954
+ }
955
+ async function executeCheck(mb, agentId, sessionId, aliases, role, i) {
956
+ const limit = i.limit ?? 20;
957
+ const markRead = i.markRead ?? true;
958
+ const completed = i.completed ?? false;
959
+ const outcome = i.outcome;
960
+ const targets = [
961
+ agentId,
962
+ ...aliases.filter((al) => al && al !== agentId),
963
+ `@session:${sessionId}`
964
+ ];
965
+ const batches = await Promise.all(
966
+ targets.map(
967
+ (to) => mb.query({ to, unreadBy: agentId, readerRole: role, limit, minPriority: "low" }).catch(() => [])
968
+ )
969
+ );
970
+ const seen = /* @__PURE__ */ new Set();
971
+ const candidates = batches.flat().filter((m) => {
972
+ if (seen.has(m.id)) return false;
973
+ if (!isMailboxMessageVisibleTo(m, agentId, role)) return false;
974
+ seen.add(m.id);
975
+ return true;
976
+ });
977
+ const messages = [];
978
+ for (const m of candidates) {
979
+ if (await acceptMailboxMessageForSession(m, sessionId)) {
980
+ messages.push(m);
981
+ }
982
+ }
983
+ const acked = markRead || completed ? await mb.ackMany({
984
+ acks: messages.map((m) => ({
985
+ messageId: m.id,
986
+ readerId: agentId,
987
+ read: markRead,
988
+ completed,
989
+ outcome: completed ? outcome : void 0
990
+ }))
991
+ }).catch(() => messages) : messages;
992
+ return {
993
+ ok: true,
994
+ count: acked.length,
995
+ messages: acked.map((m) => formatMessage(m, agentId)),
996
+ summary: acked.length === 0 ? "No unread messages." : `${acked.length} unread message(s)${markRead ? " (marked read)" : ""}${completed ? " (completed)" : ""}.`
997
+ };
998
+ }
999
+ async function executeSend(mb, identity, sessionId, i, ctx) {
1000
+ const to = i.to;
1001
+ const tp = i.type;
1002
+ const subject = i.subject;
1003
+ const body = i.body;
1004
+ const audience = i.audience;
1005
+ if (!to) return { ok: false, error: '"to" is required.' };
1006
+ if (!tp) return { ok: false, error: '"type" is required.' };
1007
+ if (!subject) return { ok: false, error: '"subject" is required.' };
1008
+ if (body === void 0 || body === null) return { ok: false, error: '"body" is required.' };
1009
+ if (audience !== void 0 && audience !== "all" && audience !== "leaders") {
1010
+ return { ok: false, error: '"audience" must be "all" or "leaders".' };
1011
+ }
1012
+ let normalizedTo;
1013
+ try {
1014
+ normalizedTo = normalizeRecipient(to, sessionId);
1015
+ } catch (err) {
1016
+ return { ok: false, error: `"to" is invalid: ${toErrorMessage(err)}` };
1017
+ }
1018
+ const delivery = applyMailboxSendPolicy(ctx, identity, normalizedTo, audience);
1019
+ const typeResult = resolveSendTypeSafe(tp, delivery.to);
1020
+ if (!typeResult.ok) return { ok: false, error: `"type" is invalid: ${typeResult.error}` };
1021
+ const msg = await mb.send({
1022
+ from: identity.callerId,
1023
+ to: delivery.to,
1024
+ type: typeResult.type,
1025
+ subject,
1026
+ body,
1027
+ audience: delivery.audience,
1028
+ priority: i.priority ?? "normal",
1029
+ replyTo: i.replyTo,
1030
+ senderSessionId: sessionId
1031
+ });
1032
+ return {
1033
+ ok: true,
1034
+ messageId: msg.id,
1035
+ to: msg.to,
1036
+ type: msg.type,
1037
+ timestamp: msg.timestamp,
1038
+ summary: `Message sent to ${msg.to === "*" ? "all agents" : msg.to}. Id: ${msg.id}`
1039
+ };
1040
+ }
1041
+ async function executeAck(mb, agentId, i) {
1042
+ const messageId = i.messageId;
1043
+ if (!messageId) return { ok: false, error: '"messageId" is required.' };
1044
+ const updated = await mb.ack({
1045
+ messageId,
1046
+ readerId: agentId,
1047
+ read: i.read,
1048
+ completed: i.completed,
1049
+ outcome: i.outcome
1050
+ });
1051
+ if (!updated) return { ok: false, error: `Message "${messageId}" not found.` };
1052
+ return {
1053
+ ok: true,
1054
+ messageId: updated.id,
1055
+ readBy: Object.keys(updated.readBy),
1056
+ readByCount: Object.keys(updated.readBy).length,
1057
+ completed: updated.completed,
1058
+ completedBy: updated.completedBy,
1059
+ outcome: updated.outcome,
1060
+ summary: `Message ${messageId} acknowledged. Read by ${Object.keys(updated.readBy).length} agent(s), Completed: ${updated.completed}.`
1061
+ };
1062
+ }
1063
+ async function executeQuery(mb, agentId, sessionId, role, i) {
1064
+ const limit = i.limit ?? 50;
1065
+ const messages = await mb.query({
1066
+ to: i.to,
1067
+ from: i.from,
1068
+ unreadBy: i.unreadBy,
1069
+ incompleteOnly: i.incompleteOnly,
1070
+ type: i.type,
1071
+ minPriority: i.minPriority,
1072
+ since: i.since,
1073
+ sessionId: i.sessionId,
1074
+ limit
1075
+ });
1076
+ const visible = [];
1077
+ for (const message of messages) {
1078
+ if (isMailboxMessageVisibleTo(message, agentId, role) && await acceptMailboxMessageForSession(message, sessionId)) {
1079
+ visible.push(message);
1080
+ }
1081
+ }
1082
+ return { ok: true, count: visible.length, messages: visible, summary: `${visible.length} message(s).` };
1083
+ }
1084
+ async function executeStatus(mb) {
1085
+ const agents = await mb.getAgentStatuses();
1086
+ return {
1087
+ ok: true,
1088
+ count: agents.length,
1089
+ agents: agents.map((a) => ({
1090
+ agentId: a.agentId,
1091
+ name: a.name,
1092
+ role: a.role,
1093
+ sessionId: a.sessionId,
1094
+ status: a.status,
1095
+ currentTool: a.currentTool,
1096
+ currentTask: a.currentTask,
1097
+ iterations: a.iterations,
1098
+ toolCalls: a.toolCalls,
1099
+ lastSeenAt: a.lastSeenAt,
1100
+ online: a.online,
1101
+ pid: a.pid,
1102
+ source: a.source
1103
+ })),
1104
+ summary: `${agents.filter((a) => a.online).length} online, ${agents.length} total.`
1105
+ };
1106
+ }
1107
+ async function executeOnline(mb) {
1108
+ const agents = await mb.getOnlineAgents();
1109
+ return {
1110
+ ok: true,
1111
+ count: agents.length,
1112
+ agents: agents.map((a) => ({
1113
+ agentId: a.agentId,
1114
+ name: a.name,
1115
+ role: a.role,
1116
+ sessionId: a.sessionId,
1117
+ status: a.status,
1118
+ currentTool: a.currentTool,
1119
+ currentTask: a.currentTask,
1120
+ lastSeenAt: a.lastSeenAt,
1121
+ source: a.source
1122
+ })),
1123
+ summary: `${agents.length} online agent(s).`
1124
+ };
1125
+ }
1126
+ async function executeUnread(mb, agentId, sessionId, aliases = [], role) {
1127
+ const targets = [
1128
+ agentId,
1129
+ ...aliases.filter((al) => al && al !== agentId),
1130
+ `@session:${sessionId}`
1131
+ ];
1132
+ const batches = await Promise.all(
1133
+ targets.map((to) => mb.query({ to, unreadBy: agentId, readerRole: role, limit: 200 }).catch(() => []))
1134
+ );
1135
+ const ids = /* @__PURE__ */ new Set();
1136
+ for (const m of batches.flat()) {
1137
+ if (!isMailboxMessageVisibleTo(m, agentId, role)) continue;
1138
+ if (await acceptMailboxMessageForSession(m, sessionId)) ids.add(m.id);
1139
+ }
1140
+ return { ok: true, count: ids.size, summary: `${ids.size} unread message(s) for you.` };
1141
+ }
1142
+ function formatMessage(m, readerId) {
1143
+ const maxBody = 2e3;
1144
+ const truncated = m.body.length > maxBody ? `${m.body.slice(0, maxBody)}\u2026 [truncated]` : m.body;
1145
+ return {
1146
+ id: m.id,
1147
+ from: m.from,
1148
+ to: m.to,
1149
+ type: m.type,
1150
+ audience: m.audience ?? "all",
1151
+ subject: m.subject,
1152
+ body: truncated,
1153
+ priority: m.priority,
1154
+ readByMe: readerId in m.readBy,
1155
+ readByCount: Object.keys(m.readBy).length,
1156
+ readBy: m.readBy,
1157
+ completed: m.completed,
1158
+ completedBy: m.completedBy,
1159
+ outcome: m.outcome,
1160
+ timestamp: m.timestamp,
1161
+ replyTo: m.replyTo,
1162
+ senderSessionId: m.senderSessionId
1163
+ };
1164
+ }
1165
+
1166
+ export {
1167
+ isMailboxProjectServerAvailable,
1168
+ MailboxProjectServerConnection,
1169
+ RemoteMailbox,
1170
+ createProjectMailbox,
1171
+ getSharedProjectMailbox,
1172
+ defaultResolveProjectDir,
1173
+ mailboxSessionTag,
1174
+ resolveMailboxIdentity,
1175
+ applyMailboxSendPolicy,
1176
+ makeMailboxTool
1177
+ };
1178
+ //# sourceMappingURL=chunk-RD6DEE36.js.map