@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
@@ -0,0 +1,1223 @@
1
+ import {
2
+ AGENT_STALE_MS,
3
+ AUTO_COMPACT_DEFAULT_TTL_MS,
4
+ AUTO_COMPACT_INTERVAL_MS,
5
+ AUTO_COMPACT_READ_MAX_AGE_MS,
6
+ AUTO_COMPACT_TYPE_TTL_MS,
7
+ CLIENT_STALE_MS,
8
+ HEARTBEAT_THROTTLE_MS,
9
+ MAX_CREDENTIAL_TTL,
10
+ ROTATION_OVERLAP_MS,
11
+ createMailboxCredential,
12
+ deleteMessages,
13
+ initializeSchema,
14
+ isFanOutRecipient,
15
+ loadDatabaseSync,
16
+ materializeMessageRows,
17
+ migrateLegacyFiles,
18
+ normalizeMailboxMessageType,
19
+ persistAgent,
20
+ persistClient,
21
+ persistCredential,
22
+ persistMessage,
23
+ persistReceipt,
24
+ pruneAgents,
25
+ pruneClients,
26
+ readAgents,
27
+ readClients,
28
+ verifyMailboxCredential,
29
+ withoutAggregateCompletion
30
+ } from "./chunk-GM54GJBU.js";
31
+ import {
32
+ acceptMailboxMessageForSession,
33
+ isMailboxLeader,
34
+ isMailboxMessageVisibleTo,
35
+ mailboxIdentityBase,
36
+ normalizeRecipient,
37
+ sessionRecipient,
38
+ validateSendType
39
+ } from "./chunk-W4A6QA3D.js";
40
+
41
+ // src/coordination/mailbox-project-server-protocol.ts
42
+ var MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION = 4;
43
+ var MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS = 16 * 1024 * 1024;
44
+ var MAILBOX_SERVER_OPERATION_NAMES = {
45
+ ping: true,
46
+ send: true,
47
+ sendRuntimeControl: true,
48
+ query: true,
49
+ ack: true,
50
+ ackMany: true,
51
+ unreadCount: true,
52
+ softDelete: true,
53
+ restore: true,
54
+ registerAgent: true,
55
+ deregisterAgent: true,
56
+ heartbeat: true,
57
+ getAgentStatuses: true,
58
+ getOnlineAgents: true,
59
+ purgeAgents: true,
60
+ registerClient: true,
61
+ deregisterClient: true,
62
+ clientHeartbeat: true,
63
+ getClientStatuses: true,
64
+ purgeClients: true,
65
+ clearAll: true,
66
+ purgeStale: true,
67
+ autoCompact: true,
68
+ credentialIssue: true,
69
+ credentialVerify: true,
70
+ credentialRevoke: true,
71
+ credentialRotate: true,
72
+ credentialGet: true,
73
+ credentialList: true,
74
+ credentialStatusCounts: true
75
+ };
76
+ function isRequestId(value) {
77
+ return Number.isSafeInteger(value) && value >= 0;
78
+ }
79
+ function isRecord(value) {
80
+ return typeof value === "object" && value !== null && !Array.isArray(value);
81
+ }
82
+ function hasString(record, key) {
83
+ return typeof record[key] === "string" && record[key].length > 0;
84
+ }
85
+ function hasRecord(record, key) {
86
+ return isRecord(record[key]);
87
+ }
88
+ function isMailboxServerOperationArgs(op, value) {
89
+ if (!isRecord(value)) return false;
90
+ switch (op) {
91
+ case "ping":
92
+ case "getAgentStatuses":
93
+ case "getOnlineAgents":
94
+ case "getClientStatuses":
95
+ case "purgeClients":
96
+ case "clearAll":
97
+ case "credentialList":
98
+ case "credentialStatusCounts":
99
+ return true;
100
+ case "send":
101
+ case "sendRuntimeControl":
102
+ case "ack":
103
+ case "ackMany":
104
+ case "registerAgent":
105
+ case "heartbeat":
106
+ case "registerClient":
107
+ case "clientHeartbeat":
108
+ return hasRecord(value, "input");
109
+ case "query":
110
+ return hasRecord(value, "query");
111
+ case "unreadCount":
112
+ return hasString(value, "forAgentId");
113
+ case "softDelete":
114
+ return hasString(value, "mailId") && hasString(value, "by");
115
+ case "restore":
116
+ return hasString(value, "mailId");
117
+ case "deregisterAgent":
118
+ return hasString(value, "agentId");
119
+ case "purgeAgents":
120
+ case "purgeStale":
121
+ case "autoCompact":
122
+ return true;
123
+ case "deregisterClient":
124
+ return hasString(value, "clientId");
125
+ case "credentialIssue":
126
+ return hasRecord(value, "options");
127
+ case "credentialVerify":
128
+ return hasString(value, "credentialId") && hasString(value, "secret");
129
+ case "credentialRevoke":
130
+ case "credentialRotate":
131
+ case "credentialGet":
132
+ return hasString(value, "credentialId");
133
+ }
134
+ }
135
+ function isMailboxProjectServerClientMessage(value) {
136
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
137
+ const message = value;
138
+ if (message["type"] === "heartbeat") return true;
139
+ if (message["type"] === "shutdown") {
140
+ return isRequestId(message["id"]) && (message["reason"] === void 0 || typeof message["reason"] === "string");
141
+ }
142
+ if (message["type"] !== "request" || !isRequestId(message["id"])) return false;
143
+ const op = message["op"];
144
+ return typeof op === "string" && Object.hasOwn(MAILBOX_SERVER_OPERATION_NAMES, op) && isMailboxServerOperationArgs(op, message["args"]);
145
+ }
146
+ function isMailboxProjectServerMessage(value) {
147
+ if (!isRecord(value) || typeof value["type"] !== "string") return false;
148
+ if (value["type"] === "hello") {
149
+ return Number.isInteger(value["protocolVersion"]) && Number.isInteger(value["pid"]) && hasString(value, "projectDir") && hasString(value, "endpoint") && hasString(value, "startedAt");
150
+ }
151
+ if (value["type"] === "event") return hasString(value, "event");
152
+ if (value["type"] === "mailbox-event") return isRecord(value["event"]);
153
+ if (value["type"] !== "response" || !isRequestId(value["id"])) return false;
154
+ if (value["ok"] === true) return Object.hasOwn(value, "result");
155
+ return value["ok"] === false && typeof value["error"] === "string";
156
+ }
157
+ function encodeMailboxProjectServerMessage(message) {
158
+ return `${JSON.stringify(message)}
159
+ `;
160
+ }
161
+
162
+ // src/coordination/mailbox-project-server-endpoint.ts
163
+ import { createHash } from "node:crypto";
164
+ import * as os from "node:os";
165
+ import * as path from "node:path";
166
+ var MAILBOX_PROJECT_SERVER_METADATA_FILE = ".mailbox-server.json";
167
+ function normalizeLocalPath(value) {
168
+ const resolved = path.resolve(value);
169
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
170
+ }
171
+ function mailboxProjectServerKey(projectDir) {
172
+ return createHash("sha256").update(normalizeLocalPath(projectDir)).digest("hex").slice(0, 24);
173
+ }
174
+ function mailboxProjectServerEndpoint(projectDir) {
175
+ const key = mailboxProjectServerKey(projectDir);
176
+ if (process.platform === "win32") {
177
+ return `\\\\.\\pipe\\wrongstack-mailbox-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}-${key}`;
178
+ }
179
+ return path.join(
180
+ os.tmpdir(),
181
+ `wsmb-v${MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION}`,
182
+ `${key}.sock`
183
+ );
184
+ }
185
+ function mailboxProjectServerMetadataPath(projectDir) {
186
+ return path.join(path.resolve(projectDir), MAILBOX_PROJECT_SERVER_METADATA_FILE);
187
+ }
188
+
189
+ // src/coordination/mailbox-credential-throttle.ts
190
+ var CREDENTIAL_VERIFY_MAX_FAILURES = 10;
191
+ var CREDENTIAL_VERIFY_WINDOW_MS = 6e4;
192
+ var CREDENTIAL_VERIFY_COOLDOWN_MS = 6e4;
193
+ var MAX_TRACKED_IDS = 4096;
194
+ var CredentialVerifyThrottle = class {
195
+ constructor(maxFailures = CREDENTIAL_VERIFY_MAX_FAILURES, windowMs = CREDENTIAL_VERIFY_WINDOW_MS, cooldownMs = CREDENTIAL_VERIFY_COOLDOWN_MS) {
196
+ this.maxFailures = maxFailures;
197
+ this.windowMs = windowMs;
198
+ this.cooldownMs = cooldownMs;
199
+ }
200
+ maxFailures;
201
+ windowMs;
202
+ cooldownMs;
203
+ records = /* @__PURE__ */ new Map();
204
+ /** Is this credential id allowed another attempt right now? */
205
+ check(credentialId, now = Date.now()) {
206
+ const record = this.records.get(credentialId);
207
+ if (record === void 0) return { allowed: true, retryAfterMs: 0 };
208
+ if (record.blockedUntil > now) {
209
+ return { allowed: false, retryAfterMs: record.blockedUntil - now };
210
+ }
211
+ return { allowed: true, retryAfterMs: 0 };
212
+ }
213
+ /** Record a failed verification. Starts the cooldown once the limit is hit. */
214
+ recordFailure(credentialId, now = Date.now()) {
215
+ const record = this.records.get(credentialId);
216
+ if (record === void 0 || now - record.windowStartedAt > this.windowMs) {
217
+ this.evictIfFull();
218
+ this.records.set(credentialId, { failures: 1, windowStartedAt: now, blockedUntil: 0 });
219
+ return;
220
+ }
221
+ record.failures += 1;
222
+ if (record.failures >= this.maxFailures) {
223
+ record.blockedUntil = now + this.cooldownMs;
224
+ record.failures = 0;
225
+ record.windowStartedAt = now;
226
+ }
227
+ }
228
+ /**
229
+ * Clear the counter after a successful verification. A legitimate holder who
230
+ * fluffed a few attempts is not left in a cooldown they cannot exit.
231
+ */
232
+ recordSuccess(credentialId) {
233
+ this.records.delete(credentialId);
234
+ }
235
+ /** Test seam. */
236
+ reset() {
237
+ this.records.clear();
238
+ }
239
+ evictIfFull() {
240
+ if (this.records.size < MAX_TRACKED_IDS) return;
241
+ const oldest = this.records.keys().next();
242
+ if (!oldest.done) this.records.delete(oldest.value);
243
+ }
244
+ };
245
+ var credentialVerifyThrottle = new CredentialVerifyThrottle();
246
+
247
+ // src/coordination/sqlite-mailbox.ts
248
+ import { randomUUID } from "node:crypto";
249
+ import * as fs from "node:fs";
250
+ import * as path2 from "node:path";
251
+
252
+ // src/coordination/global-mailbox-completion.ts
253
+ function isMailboxMessageProjection(msg) {
254
+ if (!("recipientState" in msg)) return false;
255
+ const recipientState = msg.recipientState;
256
+ return typeof recipientState === "object" && recipientState !== null && !Array.isArray(recipientState);
257
+ }
258
+ function isMessageCompletedForActor(msg, actorId) {
259
+ if (!isMailboxMessageProjection(msg)) return msg.completed === true;
260
+ if (msg.legacyGlobalCompletion) return true;
261
+ if (actorId !== void 0) {
262
+ const state = msg.recipientState[actorId];
263
+ if (state !== void 0) return state.completedAt !== void 0;
264
+ if (Object.keys(msg.recipientState).length > 0) return false;
265
+ }
266
+ return msg.completed === true;
267
+ }
268
+
269
+ // src/coordination/mailbox-retention-state.ts
270
+ function resolveMailboxRetentionState(message, agentStatuses) {
271
+ const projection = message;
272
+ if (projection.legacyGlobalCompletion) {
273
+ return { completed: true, completedAt: message.completedAt ?? message.timestamp };
274
+ }
275
+ const recipientState = projection.recipientState;
276
+ if (recipientState === void 0) {
277
+ return message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
278
+ }
279
+ const relevantRecipients = resolveRelevantRecipients(message, recipientState, agentStatuses);
280
+ if (relevantRecipients.length === 0) return { completed: false };
281
+ const completionTimes = [];
282
+ for (const actorId of relevantRecipients) {
283
+ const completedAt = recipientState[actorId]?.completedAt;
284
+ if (completedAt === void 0) return { completed: false };
285
+ completionTimes.push(completedAt);
286
+ }
287
+ return {
288
+ completed: true,
289
+ // Retention starts only after the last intended recipient completed.
290
+ completedAt: completionTimes.reduce((latest, time) => time > latest ? time : latest)
291
+ };
292
+ }
293
+ function projectMailboxCompletion(message, actorId, agentStatuses) {
294
+ const projection = message;
295
+ let state;
296
+ if (actorId === void 0) {
297
+ state = resolveMailboxRetentionState(message, agentStatuses);
298
+ } else if (projection.legacyGlobalCompletion) {
299
+ state = { completed: true, completedAt: message.completedAt ?? message.timestamp };
300
+ } else if (projection.recipientState !== void 0) {
301
+ const completedAt = projection.recipientState[actorId]?.completedAt;
302
+ state = completedAt === void 0 ? { completed: false } : { completed: true, completedAt };
303
+ } else {
304
+ state = message.completed ? { completed: true, completedAt: message.completedAt ?? message.timestamp } : { completed: false };
305
+ }
306
+ const result = {
307
+ ...projection,
308
+ completed: state.completed,
309
+ readBy: { ...message.readBy }
310
+ };
311
+ if (state.completedAt === void 0) delete result.completedAt;
312
+ else result.completedAt = state.completedAt;
313
+ return result;
314
+ }
315
+ function resolveRelevantRecipients(message, recipientState, agentStatuses) {
316
+ const statuses = agentStatuses ?? [];
317
+ const receiptActors = Object.keys(recipientState);
318
+ if (message.to === "*") {
319
+ if (statuses.length === 0) return [];
320
+ const registeredRecipients = statuses.filter((status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)).map((status) => status.agentId);
321
+ return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
322
+ }
323
+ if (message.to.startsWith("@session:")) {
324
+ if (statuses.length === 0) return [];
325
+ const sessionId = message.to.slice("@session:".length);
326
+ const registeredRecipients = statuses.filter(
327
+ (status) => status.sessionId === sessionId && isMailboxMessageVisibleTo(message, status.agentId, status.role)
328
+ ).map((status) => status.agentId);
329
+ return [.../* @__PURE__ */ new Set([...registeredRecipients, ...receiptActors])];
330
+ }
331
+ if (message.to.includes("@")) return [message.to];
332
+ if (statuses.length === 0) return [];
333
+ const aliasRecipients = statuses.filter(
334
+ (status) => (status.role?.toLowerCase() === message.to.toLowerCase() || mailboxIdentityBase(status.agentId) === message.to.toLowerCase()) && isMailboxMessageVisibleTo(message, status.agentId, status.role)
335
+ ).map((status) => status.agentId);
336
+ return [.../* @__PURE__ */ new Set([...aliasRecipients, ...receiptActors])];
337
+ }
338
+
339
+ // src/coordination/mailbox-status-mappers.ts
340
+ function mapRegisteredAgentsToStatuses(registry, now, staleMs) {
341
+ return Array.from(registry.values()).map((agent) => ({
342
+ agentId: agent.agentId,
343
+ name: agent.name,
344
+ role: agent.role,
345
+ sessionId: agent.sessionId,
346
+ status: agent.status,
347
+ currentTool: agent.currentTool,
348
+ currentTask: agent.currentTask,
349
+ iterations: agent.iterations,
350
+ toolCalls: agent.toolCalls,
351
+ lastActivityAt: agent.lastSeenAt,
352
+ lastSeenAt: agent.lastSeenAt,
353
+ online: now - new Date(agent.lastSeenAt).getTime() < staleMs,
354
+ pid: agent.pid,
355
+ source: agent.source
356
+ })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
357
+ }
358
+ function mapRegisteredClientsToStatuses(registry, now, staleMs) {
359
+ return Array.from(registry.values()).map((client) => ({
360
+ clientId: client.clientId,
361
+ name: client.name,
362
+ source: client.source,
363
+ sessionId: client.sessionId,
364
+ lastSeenAt: client.lastSeenAt,
365
+ online: now - new Date(client.lastSeenAt).getTime() < staleMs,
366
+ pid: client.pid
367
+ })).sort((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
368
+ }
369
+
370
+ // src/coordination/sqlite-mailbox-compaction.ts
371
+ async function purgeStale(ctx, options) {
372
+ const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
373
+ const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
374
+ const statuses = await ctx.getAgentStatuses();
375
+ const now = Date.now();
376
+ let completedPurged = 0;
377
+ let incompletePurged = 0;
378
+ const ids = [];
379
+ const messages = ctx.readMessages();
380
+ for (const message of messages) {
381
+ const retention = resolveMailboxRetentionState(message, statuses);
382
+ const messageTime = new Date(message.timestamp).getTime();
383
+ const completionTime = new Date(retention.completedAt ?? 0).getTime();
384
+ if (retention.completed && completionTime < now - completedMaxAgeMs) {
385
+ completedPurged++;
386
+ ids.push(message.id);
387
+ } else if (!retention.completed && messageTime < now - incompleteMaxAgeMs) {
388
+ incompletePurged++;
389
+ ids.push(message.id);
390
+ }
391
+ }
392
+ ctx.deleteMessages(ids);
393
+ return {
394
+ completedPurged,
395
+ incompletePurged,
396
+ totalPurged: ids.length,
397
+ remaining: messages.length - ids.length
398
+ };
399
+ }
400
+ async function autoCompact(ctx, options) {
401
+ const readMaxAgeMs = options?.readMaxAgeMs ?? AUTO_COMPACT_READ_MAX_AGE_MS;
402
+ const defaultTtlMs = options?.defaultTtlMs ?? AUTO_COMPACT_DEFAULT_TTL_MS;
403
+ const typeTtlMs = options?.typeTtlMs ?? AUTO_COMPACT_TYPE_TTL_MS;
404
+ const completedMaxAgeMs = options?.completedMaxAgeMs ?? 864e5;
405
+ const incompleteMaxAgeMs = options?.incompleteMaxAgeMs ?? 6048e5;
406
+ const statuses = await ctx.getAgentStatuses();
407
+ const online = statuses.filter((status) => status.online);
408
+ const now = Date.now();
409
+ let readByAllRemoved = 0;
410
+ let expiredRemoved = 0;
411
+ let stalePurged = 0;
412
+ const ids = [];
413
+ const messages = ctx.readMessages();
414
+ for (const message of messages) {
415
+ const messageTime = new Date(message.timestamp).getTime();
416
+ const expiry = message.expiresAt !== void 0 ? new Date(message.expiresAt).getTime() : messageTime + (typeTtlMs[message.type] ?? defaultTtlMs);
417
+ if (expiry < now) {
418
+ expiredRemoved++;
419
+ ids.push(message.id);
420
+ continue;
421
+ }
422
+ const retention = resolveMailboxRetentionState(message, statuses);
423
+ const eligible = online.filter(
424
+ (status) => isMailboxMessageVisibleTo(message, status.agentId, status.role)
425
+ );
426
+ if (!retention.completed && eligible.length > 0) {
427
+ const readByAll = eligible.every((status) => status.agentId in message.readBy);
428
+ const latestRead = Math.max(
429
+ ...eligible.map((status) => new Date(message.readBy[status.agentId] ?? 0).getTime())
430
+ );
431
+ if (readByAll && latestRead < now - readMaxAgeMs) {
432
+ readByAllRemoved++;
433
+ ids.push(message.id);
434
+ continue;
435
+ }
436
+ }
437
+ const completionTime = new Date(retention.completedAt ?? 0).getTime();
438
+ if (retention.completed && completionTime < now - completedMaxAgeMs || !retention.completed && messageTime < now - incompleteMaxAgeMs) {
439
+ stalePurged++;
440
+ ids.push(message.id);
441
+ }
442
+ }
443
+ ctx.deleteMessages(ids);
444
+ return {
445
+ readByAllRemoved,
446
+ expiredRemoved,
447
+ stalePurged,
448
+ totalRemoved: ids.length,
449
+ remaining: messages.length - ids.length
450
+ };
451
+ }
452
+
453
+ // src/coordination/sqlite-mailbox-credentials.ts
454
+ function credentialGet(db, credentialId) {
455
+ const row = db.prepare("SELECT data FROM credentials WHERE credential_id = ?").get(credentialId);
456
+ return row === void 0 ? null : JSON.parse(row.data);
457
+ }
458
+ function credentialList(db) {
459
+ const rows = db.prepare("SELECT data FROM credentials").all();
460
+ return rows.map((row) => JSON.parse(row.data)).sort((left, right) => {
461
+ if (left.status === "active" && right.status !== "active") return -1;
462
+ if (left.status !== "active" && right.status === "active") return 1;
463
+ return right.issuedAt.localeCompare(left.issuedAt);
464
+ });
465
+ }
466
+ function credentialStatusCounts(db) {
467
+ const rows = db.prepare("SELECT status, COUNT(*) AS count FROM credentials GROUP BY status").all();
468
+ return Object.fromEntries(rows.map((row) => [row.status, row.count]));
469
+ }
470
+ function credentialIssue(db, transaction, options) {
471
+ const now = Date.now();
472
+ const issued = createMailboxCredential(options, now);
473
+ transaction(() => {
474
+ if (options.supersedes !== void 0) {
475
+ const old = credentialGet(db, options.supersedes);
476
+ if (old?.status === "active") {
477
+ old.status = "rotated_out";
478
+ old.statusChangedAt = new Date(now).toISOString();
479
+ old.statusReason = "superseded by rotation";
480
+ old.rotationValidUntil = new Date(now + ROTATION_OVERLAP_MS).toISOString();
481
+ persistCredential(db, old);
482
+ }
483
+ }
484
+ persistCredential(db, issued.credential);
485
+ });
486
+ return issued;
487
+ }
488
+ function credentialVerify(db, credentialId, secret) {
489
+ const decision = credentialVerifyThrottle.check(credentialId);
490
+ if (!decision.allowed) {
491
+ return {
492
+ valid: false,
493
+ reason: `too many failed verification attempts \u2014 retry in ${Math.ceil(decision.retryAfterMs / 1e3)}s`
494
+ };
495
+ }
496
+ const result = verifyMailboxCredential(credentialGet(db, credentialId) ?? void 0, secret);
497
+ if (result.valid) credentialVerifyThrottle.recordSuccess(credentialId);
498
+ else credentialVerifyThrottle.recordFailure(credentialId);
499
+ return result;
500
+ }
501
+ function credentialRevoke(db, credentialId, reason, by) {
502
+ const credential = credentialGet(db, credentialId);
503
+ if (credential === null || credential.status === "revoked") return false;
504
+ credential.status = "revoked";
505
+ credential.statusChangedAt = (/* @__PURE__ */ new Date()).toISOString();
506
+ credential.statusReason = reason ?? "revoked";
507
+ credential.lastModifiedBy = by;
508
+ persistCredential(db, credential);
509
+ return true;
510
+ }
511
+ function credentialRotate(db, transaction, credentialId, options) {
512
+ const old = credentialGet(db, credentialId);
513
+ if (old === null) return null;
514
+ if (old.status === "revoked") return null;
515
+ return credentialIssue(db, transaction, {
516
+ principalId: old.principalId,
517
+ projectId: old.projectId ?? options?.projectId,
518
+ kind: old.kind,
519
+ capabilities: options?.capabilities ?? old.capabilities,
520
+ ttlMs: options?.ttlMs ?? previousCredentialTtlMs(old),
521
+ notBefore: options?.notBefore,
522
+ supersedes: credentialId,
523
+ issuedBy: options?.issuedBy
524
+ });
525
+ }
526
+ function previousCredentialTtlMs(credential) {
527
+ const issuedAt = new Date(credential.issuedAt).getTime();
528
+ const expiresAt = new Date(credential.expiresAt).getTime();
529
+ const ttlMs = expiresAt - issuedAt;
530
+ return Number.isFinite(ttlMs) && ttlMs > 0 ? ttlMs : MAX_CREDENTIAL_TTL[credential.kind];
531
+ }
532
+
533
+ // src/coordination/sqlite-mailbox.ts
534
+ var SQLITE_MAILBOX_FILE = "_mailbox.sqlite";
535
+ var HEARTBEAT_TRACKING_MAX_ENTRIES = 512;
536
+ var HEARTBEAT_TRACKING_TTL_MS = 30 * 6e4;
537
+ var SqliteMailbox = class {
538
+ constructor(projectDir, events, eventEmitter) {
539
+ this.projectDir = projectDir;
540
+ fs.mkdirSync(projectDir, { recursive: true });
541
+ this.databasePath = path2.join(projectDir, SQLITE_MAILBOX_FILE);
542
+ this.messagePath = this.databasePath;
543
+ this.events = events;
544
+ this.eventEmitter = eventEmitter;
545
+ const Database = loadDatabaseSync();
546
+ this.db = new Database(this.databasePath);
547
+ this.db.exec("PRAGMA journal_mode = WAL");
548
+ this.db.exec("PRAGMA synchronous = NORMAL");
549
+ this.db.exec("PRAGMA foreign_keys = ON");
550
+ this.db.exec("PRAGMA busy_timeout = 5000");
551
+ initializeSchema(this.schemaCtx());
552
+ migrateLegacyFiles(this.schemaCtx());
553
+ }
554
+ projectDir;
555
+ databasePath;
556
+ /** Compatibility alias used by project-server health/status consumers. */
557
+ messagePath;
558
+ eventEmitter;
559
+ db;
560
+ events;
561
+ lastHeartbeat = /* @__PURE__ */ new Map();
562
+ lastClientHeartbeat = /* @__PURE__ */ new Map();
563
+ autoCompactTimer = null;
564
+ autoCompactInFlight;
565
+ closed = false;
566
+ stmt(sql) {
567
+ return this.db.prepare(sql);
568
+ }
569
+ transaction(run) {
570
+ this.db.exec("BEGIN IMMEDIATE");
571
+ try {
572
+ const result = run();
573
+ this.db.exec("COMMIT");
574
+ return result;
575
+ } catch (error) {
576
+ this.db.exec("ROLLBACK");
577
+ throw error;
578
+ }
579
+ }
580
+ /** Bundle of store state the schema/migration module operates on. */
581
+ schemaCtx() {
582
+ return {
583
+ db: this.db,
584
+ projectDir: this.projectDir,
585
+ transaction: (run) => this.transaction(run)
586
+ };
587
+ }
588
+ persistMessage(message, legacyGlobalCompletion = false) {
589
+ persistMessage(this.db, message, legacyGlobalCompletion);
590
+ }
591
+ persistReceipt(messageId, state) {
592
+ persistReceipt(this.db, messageId, state);
593
+ }
594
+ materializeMessageRows(rows) {
595
+ return materializeMessageRows(this.db, rows);
596
+ }
597
+ readMessages() {
598
+ const rows = this.stmt(
599
+ "SELECT id, data, legacy_global_completion FROM messages"
600
+ ).all();
601
+ return this.materializeMessageRows(rows);
602
+ }
603
+ findMessage(messageId) {
604
+ const row = this.stmt(
605
+ "SELECT id, data, legacy_global_completion FROM messages WHERE id = ?"
606
+ ).get(messageId);
607
+ return row === void 0 ? void 0 : this.materializeMessageRows([row])[0];
608
+ }
609
+ async send(input) {
610
+ return this.sendMessage(input, false);
611
+ }
612
+ async sendRuntimeControl(input) {
613
+ return this.sendMessage({ ...input, type: "control" }, true);
614
+ }
615
+ async sendMessage(input, allowRuntimeControl) {
616
+ const type = normalizeMailboxMessageType(input.type);
617
+ const to = normalizeRecipient(input.to, input.senderSessionId);
618
+ if (!(allowRuntimeControl && type === "control")) validateSendType(type, to);
619
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
620
+ const message = {
621
+ id: randomUUID(),
622
+ from: input.from,
623
+ to,
624
+ type,
625
+ ...input.audience !== void 0 && input.audience !== "all" ? { audience: input.audience } : {},
626
+ subject: input.subject,
627
+ body: input.body,
628
+ priority: input.priority ?? "normal",
629
+ readBy: {},
630
+ completed: false,
631
+ timestamp,
632
+ ...input.replyTo !== void 0 ? { replyTo: input.replyTo } : {},
633
+ ...input.taskContext !== void 0 ? { taskContext: input.taskContext } : {},
634
+ ...input.senderSessionId !== void 0 ? { senderSessionId: input.senderSessionId } : {},
635
+ ...input.sessionAffinity !== void 0 ? { sessionAffinity: input.sessionAffinity } : {},
636
+ ...input.ttlMs !== void 0 ? { expiresAt: new Date(Date.now() + input.ttlMs).toISOString() } : {}
637
+ };
638
+ this.persistMessage(message);
639
+ this.events?.emitCustom("mailbox.message_sent", {
640
+ messageId: message.id,
641
+ from: message.from,
642
+ to: message.to,
643
+ type: message.type,
644
+ subject: message.subject
645
+ });
646
+ this.eventEmitter?.emit({
647
+ type: "message.sent",
648
+ messageId: message.id,
649
+ from: message.from,
650
+ to: message.to,
651
+ audience: message.audience,
652
+ timestamp
653
+ });
654
+ return message;
655
+ }
656
+ async query(query) {
657
+ const type = query.type === void 0 ? void 0 : normalizeMailboxMessageType(query.type);
658
+ const priorityRank = { low: 0, normal: 1, high: 2 };
659
+ const minimumRank = query.minPriority === void 0 ? 0 : priorityRank[query.minPriority];
660
+ if (query.ids !== void 0 && query.ids.length === 0) return [];
661
+ const statuses = query.unreadBy === void 0 ? await this.getAgentStatuses() : void 0;
662
+ const where = [];
663
+ const params = [];
664
+ if (query.ids !== void 0) {
665
+ where.push(`id IN (${query.ids.map(() => "?").join(", ")})`);
666
+ params.push(...query.ids);
667
+ }
668
+ if (query.to !== void 0) {
669
+ where.push("(to_id = ? OR to_id = ?)");
670
+ params.push(query.to, "*");
671
+ }
672
+ if (query.from !== void 0) {
673
+ where.push("from_id = ?");
674
+ params.push(query.from);
675
+ }
676
+ if (query.sessionId !== void 0) {
677
+ where.push("sender_session_id = ?");
678
+ params.push(query.sessionId);
679
+ }
680
+ if (type !== void 0) {
681
+ where.push("type = ?");
682
+ params.push(type);
683
+ }
684
+ if (query.minPriority !== void 0) {
685
+ where.push(`CASE priority WHEN 'high' THEN 2 WHEN 'low' THEN 0 ELSE 1 END >= ?`);
686
+ params.push(minimumRank);
687
+ }
688
+ if (query.since !== void 0) {
689
+ where.push("timestamp > ?");
690
+ params.push(query.since);
691
+ }
692
+ if (!query.includeDeleted) where.push("deleted_at IS NULL");
693
+ if (query.replyTo !== void 0) {
694
+ where.push("reply_to = ?");
695
+ params.push(query.replyTo);
696
+ }
697
+ if (query.unreadBy !== void 0) {
698
+ if (!isMailboxLeader(query.unreadBy, query.readerRole)) {
699
+ where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
700
+ }
701
+ if (!query.incompleteOnly) {
702
+ where.push(`NOT EXISTS (
703
+ SELECT 1 FROM message_receipts AS unread_receipt
704
+ WHERE unread_receipt.message_id = messages.id
705
+ AND unread_receipt.actor_id = ?
706
+ AND unread_receipt.read_at IS NOT NULL
707
+ )`);
708
+ params.push(query.unreadBy);
709
+ where.push(`NOT EXISTS (
710
+ SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
711
+ WHERE legacy_read.key = ?
712
+ )`);
713
+ params.push(query.unreadBy);
714
+ } else {
715
+ where.push("legacy_global_completion = 0");
716
+ where.push(`NOT EXISTS (
717
+ SELECT 1 FROM message_receipts AS completed_receipt
718
+ WHERE completed_receipt.message_id = messages.id
719
+ AND completed_receipt.actor_id = ?
720
+ AND completed_receipt.completed_at IS NOT NULL
721
+ )`);
722
+ params.push(query.unreadBy);
723
+ where.push(`(
724
+ completed = 0 OR EXISTS (
725
+ SELECT 1 FROM message_receipts AS any_receipt
726
+ WHERE any_receipt.message_id = messages.id
727
+ )
728
+ )`);
729
+ }
730
+ }
731
+ const canPreLimit = !query.incompleteOnly || query.unreadBy !== void 0;
732
+ let sql = "SELECT id, data, legacy_global_completion FROM messages";
733
+ if (where.length > 0) sql += ` WHERE ${where.join(" AND ")}`;
734
+ sql += " ORDER BY timestamp DESC, rowid DESC";
735
+ if (canPreLimit) {
736
+ sql += " LIMIT ?";
737
+ params.push(query.limit ?? 50);
738
+ }
739
+ const rows = this.stmt(sql).all(...params);
740
+ const idFilter = query.ids === void 0 ? void 0 : new Set(query.ids);
741
+ const filtered = this.materializeMessageRows(rows).filter((message) => {
742
+ if (idFilter !== void 0 && !idFilter.has(message.id)) return false;
743
+ if (query.to !== void 0 && message.to !== query.to && message.to !== "*") return false;
744
+ if (query.from !== void 0 && message.from !== query.from) return false;
745
+ if (query.sessionId !== void 0 && message.senderSessionId !== query.sessionId)
746
+ return false;
747
+ if (query.unreadBy !== void 0 && !isMailboxMessageVisibleTo(message, query.unreadBy, query.readerRole))
748
+ return false;
749
+ if (!query.incompleteOnly && query.unreadBy !== void 0 && query.unreadBy in message.readBy)
750
+ return false;
751
+ if (query.incompleteOnly && (query.unreadBy === void 0 ? projectMailboxCompletion(message, void 0, statuses).completed : isMessageCompletedForActor(message, query.unreadBy)))
752
+ return false;
753
+ if (type !== void 0 && message.type !== type) return false;
754
+ if (priorityRank[message.priority] < minimumRank) return false;
755
+ if (query.since !== void 0 && message.timestamp <= query.since) return false;
756
+ if (!query.includeDeleted && message.deletedAt !== void 0) return false;
757
+ if (query.replyTo !== void 0 && message.replyTo !== query.replyTo) return false;
758
+ return true;
759
+ });
760
+ let messages = filtered;
761
+ if (query.currentSessionId !== void 0 || query.sessionAffinityCtx !== void 0) {
762
+ const kept = [];
763
+ for (const message of filtered) {
764
+ if (await acceptMailboxMessageForSession(
765
+ message,
766
+ query.currentSessionId,
767
+ query.sessionAffinityCtx
768
+ )) {
769
+ kept.push(message);
770
+ }
771
+ }
772
+ messages = kept;
773
+ }
774
+ messages.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
775
+ return messages.slice(0, query.limit ?? 50).map((message) => {
776
+ const copy = {
777
+ ...projectMailboxCompletion(message, query.unreadBy, statuses),
778
+ readBy: { ...message.readBy }
779
+ };
780
+ if (!query.includeReceiptState) {
781
+ delete copy.recipientState;
782
+ delete copy.legacyGlobalCompletion;
783
+ }
784
+ return copy;
785
+ });
786
+ }
787
+ async ack(input) {
788
+ const results = await this.ackMany({ acks: [input] });
789
+ return results[0] ?? null;
790
+ }
791
+ async ackMany(input) {
792
+ if (input.acks.length === 0) return [];
793
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
794
+ const changed = /* @__PURE__ */ new Set();
795
+ const updated = this.transaction(() => {
796
+ const results = [];
797
+ for (const ack of input.acks) {
798
+ const message = this.findMessage(ack.messageId);
799
+ if (message === void 0) continue;
800
+ const current = message.recipientState[ack.readerId] ?? { actorId: ack.readerId };
801
+ const state = { ...current };
802
+ let didChange = false;
803
+ if (ack.read !== false && state.readAt === void 0) {
804
+ state.readAt = timestamp;
805
+ message.readBy[ack.readerId] = timestamp;
806
+ didChange = true;
807
+ }
808
+ if (ack.completed === true && state.completedAt === void 0 && message.legacyGlobalCompletion !== true) {
809
+ state.completedAt = timestamp;
810
+ state.completedBy = ack.readerId;
811
+ didChange = true;
812
+ }
813
+ if (ack.read === false && ack.completed === false && state.completedAt !== void 0) {
814
+ delete state.completedAt;
815
+ delete state.completedBy;
816
+ didChange = true;
817
+ }
818
+ if (ack.outcome !== void 0 && state.outcome !== ack.outcome) {
819
+ state.outcome = ack.outcome;
820
+ didChange = true;
821
+ }
822
+ message.recipientState = {
823
+ ...message.recipientState,
824
+ [ack.readerId]: state
825
+ };
826
+ const actorCompleted = state.completedAt !== void 0;
827
+ message.completed = message.legacyGlobalCompletion === true || actorCompleted;
828
+ if (actorCompleted) {
829
+ message.completedBy = state.completedBy ?? ack.readerId;
830
+ message.completedAt = state.completedAt;
831
+ } else if (message.legacyGlobalCompletion !== true) {
832
+ delete message.completedBy;
833
+ delete message.completedAt;
834
+ }
835
+ message.outcome = state.outcome;
836
+ if (didChange) {
837
+ this.persistReceipt(message.id, state);
838
+ this.persistMessage(
839
+ isFanOutRecipient(message.to) ? withoutAggregateCompletion(message) : message,
840
+ message.legacyGlobalCompletion === true
841
+ );
842
+ changed.add(message.id);
843
+ }
844
+ results.push({ ...message, readBy: { ...message.readBy } });
845
+ }
846
+ return results;
847
+ });
848
+ for (const message of updated) {
849
+ if (!changed.has(message.id)) continue;
850
+ this.eventEmitter?.emit({
851
+ type: "message.acked",
852
+ messageId: message.id,
853
+ from: message.from,
854
+ to: message.to,
855
+ audience: message.audience,
856
+ timestamp
857
+ });
858
+ }
859
+ return updated;
860
+ }
861
+ /**
862
+ * Count the messages this actor has neither read nor completed.
863
+ *
864
+ * Pushed into SQL rather than filtering `readMessages()`. The pre-tool hook
865
+ * asks for this repeatedly — `mailbox-hooks.ts` throttles it to once a
866
+ * second, which bounds the frequency but not the cost — and the JS form
867
+ * materialized EVERY row in `messages`, joined the whole `message_receipts`
868
+ * table, and folded per-actor receipt state across all of them just to
869
+ * return an integer.
870
+ *
871
+ * The predicate is deliberately the same one {@link query} builds for
872
+ * `{ unreadBy, incompleteOnly }`, because it has to agree exactly with
873
+ * `isMessageCompletedForActor` and `isMailboxMessageVisibleTo`:
874
+ *
875
+ * - **unread** — no per-actor receipt carrying `read_at`, and no legacy
876
+ * `readBy` key. Both are checked: `ackMany` writes the receipt row AND
877
+ * mirrors the timestamp into the message's `readBy` JSON.
878
+ * - **incomplete** — not `legacy_global_completion`, no per-actor receipt
879
+ * carrying `completed_at`, and the aggregate `completed` flag counts only
880
+ * when the message has no receipts at all (once any actor has a receipt,
881
+ * completion is per-actor and the aggregate flag is not authoritative).
882
+ * - **audience** — `leaders` mail is invisible unless the actor's base
883
+ * identity is `leader`. This call path carries no role, matching the
884
+ * `isMailboxMessageVisibleTo(message, forAgentId)` it replaces.
885
+ */
886
+ async unreadCount(forAgentId, sessionId, ctx) {
887
+ const sessionAddress = sessionId === void 0 ? void 0 : sessionRecipient(sessionId);
888
+ const where = [];
889
+ const params = [];
890
+ const recipients = ["to_id = ?", "to_id = '*'"];
891
+ params.push(forAgentId);
892
+ if (sessionAddress !== void 0) {
893
+ recipients.push("to_id = ?");
894
+ params.push(sessionAddress);
895
+ }
896
+ where.push(`(${recipients.join(" OR ")})`);
897
+ where.push("deleted_at IS NULL");
898
+ if (!isMailboxLeader(forAgentId)) {
899
+ where.push("COALESCE(json_extract(data, '$.audience'), 'all') <> 'leaders'");
900
+ }
901
+ where.push(`NOT EXISTS (
902
+ SELECT 1 FROM message_receipts AS read_receipt
903
+ WHERE read_receipt.message_id = messages.id
904
+ AND read_receipt.actor_id = ?
905
+ AND read_receipt.read_at IS NOT NULL
906
+ )`);
907
+ params.push(forAgentId);
908
+ where.push(`NOT EXISTS (
909
+ SELECT 1 FROM json_each(json_extract(data, '$.readBy')) AS legacy_read
910
+ WHERE legacy_read.key = ?
911
+ )`);
912
+ params.push(forAgentId);
913
+ where.push("legacy_global_completion = 0");
914
+ where.push(`NOT EXISTS (
915
+ SELECT 1 FROM message_receipts AS completed_receipt
916
+ WHERE completed_receipt.message_id = messages.id
917
+ AND completed_receipt.actor_id = ?
918
+ AND completed_receipt.completed_at IS NOT NULL
919
+ )`);
920
+ params.push(forAgentId);
921
+ where.push(`(
922
+ completed = 0 OR EXISTS (
923
+ SELECT 1 FROM message_receipts AS any_receipt
924
+ WHERE any_receipt.message_id = messages.id
925
+ )
926
+ )`);
927
+ if (sessionId === void 0 && ctx === void 0) {
928
+ const row = this.stmt(
929
+ `SELECT COUNT(*) AS total FROM messages WHERE ${where.join(" AND ")}`
930
+ ).get(...params);
931
+ return Number(row?.total ?? 0);
932
+ }
933
+ const rows = this.stmt(
934
+ `SELECT id, data, legacy_global_completion FROM messages WHERE ${where.join(" AND ")}`
935
+ ).all(...params);
936
+ let total = 0;
937
+ for (const message of this.materializeMessageRows(rows)) {
938
+ if (await acceptMailboxMessageForSession(message, sessionId, ctx)) total += 1;
939
+ }
940
+ return total;
941
+ }
942
+ async softDelete(mailId, by) {
943
+ const message = this.findMessage(mailId);
944
+ if (message === void 0) return null;
945
+ if (message.deletedAt !== void 0) return { ...message, readBy: { ...message.readBy } };
946
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
947
+ message.deletedAt = timestamp;
948
+ message.deletedBy = by;
949
+ const previousState = message.recipientState[by] ?? { actorId: by };
950
+ const state = {
951
+ ...previousState,
952
+ readAt: previousState.readAt ?? timestamp
953
+ };
954
+ message.readBy[by] = state.readAt;
955
+ message.recipientState = { ...message.recipientState, [by]: state };
956
+ this.transaction(() => {
957
+ this.persistReceipt(message.id, state);
958
+ this.persistMessage(message, message.legacyGlobalCompletion === true);
959
+ });
960
+ this.eventEmitter?.emit({
961
+ type: "message.deleted",
962
+ messageId: message.id,
963
+ from: message.from,
964
+ to: message.to,
965
+ audience: message.audience,
966
+ timestamp
967
+ });
968
+ return { ...message, readBy: { ...message.readBy } };
969
+ }
970
+ async restore(mailId) {
971
+ const message = this.findMessage(mailId);
972
+ if (message === void 0) return null;
973
+ if (message.deletedAt === void 0 && message.deletedBy === void 0) {
974
+ return { ...message, readBy: { ...message.readBy } };
975
+ }
976
+ delete message.deletedAt;
977
+ delete message.deletedBy;
978
+ this.persistMessage(message, message.legacyGlobalCompletion === true);
979
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
980
+ this.eventEmitter?.emit({
981
+ type: "message.restored",
982
+ messageId: message.id,
983
+ from: message.from,
984
+ to: message.to,
985
+ audience: message.audience,
986
+ timestamp
987
+ });
988
+ return { ...message, readBy: { ...message.readBy } };
989
+ }
990
+ persistAgent(agent) {
991
+ persistAgent(this.db, agent);
992
+ }
993
+ readAgents() {
994
+ return readAgents(this.db);
995
+ }
996
+ pruneAgents(maxAgeMs = AGENT_STALE_MS) {
997
+ return pruneAgents(this.db, maxAgeMs);
998
+ }
999
+ async registerAgent(input) {
1000
+ this.pruneAgents();
1001
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1002
+ this.persistAgent({
1003
+ agentId: input.agentId,
1004
+ sessionId: input.sessionId,
1005
+ name: input.name,
1006
+ ...input.role !== void 0 ? { role: input.role } : {},
1007
+ status: "idle",
1008
+ iterations: 0,
1009
+ toolCalls: 0,
1010
+ registeredAt: now,
1011
+ lastSeenAt: now,
1012
+ pid: input.pid ?? process.pid,
1013
+ ...input.source !== void 0 ? { source: input.source } : {}
1014
+ });
1015
+ this.events?.emitCustom("mailbox.agent_registered", {
1016
+ agentId: input.agentId,
1017
+ sessionId: input.sessionId,
1018
+ name: input.name,
1019
+ role: input.role,
1020
+ source: input.source
1021
+ });
1022
+ }
1023
+ /**
1024
+ * Throttle bookkeeping only: `agentId`/`clientId` -> last accepted heartbeat.
1025
+ *
1026
+ * Entries are deleted on a clean deregister, but a crashed or forcibly killed
1027
+ * peer never deregisters, so over a long-lived daemon's life these maps grew
1028
+ * with every distinct id ever seen. Dropping a stale entry is free: the next
1029
+ * heartbeat from that id simply is not throttled and writes once more.
1030
+ */
1031
+ pruneHeartbeats(map, nowMs) {
1032
+ for (const [id, at] of map) {
1033
+ if (nowMs - at > HEARTBEAT_TRACKING_TTL_MS) map.delete(id);
1034
+ }
1035
+ while (map.size > HEARTBEAT_TRACKING_MAX_ENTRIES) {
1036
+ const oldest = map.keys().next().value;
1037
+ if (oldest === void 0) break;
1038
+ map.delete(oldest);
1039
+ }
1040
+ }
1041
+ async deregisterAgent(agentId) {
1042
+ this.stmt("DELETE FROM agents WHERE agent_id = ?").run(agentId);
1043
+ this.lastHeartbeat.delete(agentId);
1044
+ this.events?.emitCustom("mailbox.agent_deregistered", { agentId });
1045
+ }
1046
+ async heartbeat(input) {
1047
+ const nowMs = Date.now();
1048
+ if (nowMs - (this.lastHeartbeat.get(input.agentId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
1049
+ this.lastHeartbeat.set(input.agentId, nowMs);
1050
+ this.pruneHeartbeats(this.lastHeartbeat, nowMs);
1051
+ this.pruneAgents();
1052
+ const agent = this.readAgents().get(input.agentId);
1053
+ if (agent !== void 0) {
1054
+ agent.lastSeenAt = new Date(nowMs).toISOString();
1055
+ if (input.status !== void 0) agent.status = input.status;
1056
+ if (input.currentTool !== void 0) agent.currentTool = input.currentTool;
1057
+ if (input.currentTask !== void 0) agent.currentTask = input.currentTask;
1058
+ if (input.iterations !== void 0) agent.iterations = input.iterations;
1059
+ if (input.toolCalls !== void 0) agent.toolCalls = input.toolCalls;
1060
+ this.persistAgent(agent);
1061
+ }
1062
+ this.events?.emitCustom("mailbox.agent_heartbeat", {
1063
+ agentId: input.agentId,
1064
+ status: input.status,
1065
+ currentTool: input.currentTool,
1066
+ currentTask: input.currentTask
1067
+ });
1068
+ }
1069
+ async getAgentStatuses() {
1070
+ this.pruneAgents();
1071
+ return mapRegisteredAgentsToStatuses(this.readAgents(), Date.now(), AGENT_STALE_MS);
1072
+ }
1073
+ async purgeAgents(maxAgeMs = AGENT_STALE_MS) {
1074
+ return this.pruneAgents(maxAgeMs);
1075
+ }
1076
+ async getOnlineAgents() {
1077
+ return (await this.getAgentStatuses()).filter((agent) => agent.online);
1078
+ }
1079
+ persistClient(client) {
1080
+ persistClient(this.db, client);
1081
+ }
1082
+ readClients() {
1083
+ return readClients(this.db);
1084
+ }
1085
+ pruneClientsInPlace() {
1086
+ return pruneClients(this.db);
1087
+ }
1088
+ async registerClient(input) {
1089
+ this.pruneClientsInPlace();
1090
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1091
+ this.persistClient({
1092
+ clientId: input.clientId,
1093
+ sessionId: input.sessionId,
1094
+ name: input.name,
1095
+ source: input.source,
1096
+ registeredAt: now,
1097
+ lastSeenAt: now,
1098
+ pid: input.pid ?? process.pid
1099
+ });
1100
+ this.events?.emitCustom("mailbox.client_registered", {
1101
+ clientId: input.clientId,
1102
+ sessionId: input.sessionId,
1103
+ name: input.name,
1104
+ source: input.source
1105
+ });
1106
+ }
1107
+ async deregisterClient(clientId) {
1108
+ this.stmt("DELETE FROM clients WHERE client_id = ?").run(clientId);
1109
+ this.lastClientHeartbeat.delete(clientId);
1110
+ this.events?.emitCustom("mailbox.client_deregistered", { clientId });
1111
+ }
1112
+ async clientHeartbeat(input) {
1113
+ const nowMs = Date.now();
1114
+ if (nowMs - (this.lastClientHeartbeat.get(input.clientId) ?? 0) < HEARTBEAT_THROTTLE_MS) return;
1115
+ this.lastClientHeartbeat.set(input.clientId, nowMs);
1116
+ this.pruneHeartbeats(this.lastClientHeartbeat, nowMs);
1117
+ this.pruneClientsInPlace();
1118
+ const client = this.readClients().get(input.clientId);
1119
+ if (client !== void 0) {
1120
+ client.lastSeenAt = new Date(nowMs).toISOString();
1121
+ if (input.sessionId) client.sessionId = input.sessionId;
1122
+ this.persistClient(client);
1123
+ }
1124
+ this.events?.emitCustom("mailbox.client_heartbeat", {
1125
+ clientId: input.clientId,
1126
+ ...input.sessionId ? { sessionId: input.sessionId } : {}
1127
+ });
1128
+ }
1129
+ async getClientStatuses() {
1130
+ this.pruneClientsInPlace();
1131
+ return mapRegisteredClientsToStatuses(this.readClients(), Date.now(), CLIENT_STALE_MS);
1132
+ }
1133
+ async purgeClients() {
1134
+ return this.pruneClientsInPlace();
1135
+ }
1136
+ async clearAll() {
1137
+ this.stmt("DELETE FROM messages").run();
1138
+ }
1139
+ async purgeStale(options) {
1140
+ return purgeStale(this.compactionCtx(), options);
1141
+ }
1142
+ async autoCompact(options) {
1143
+ if (this.autoCompactInFlight !== void 0) return this.autoCompactInFlight;
1144
+ const inFlight = autoCompact(this.compactionCtx(), options);
1145
+ this.autoCompactInFlight = inFlight;
1146
+ try {
1147
+ return await inFlight;
1148
+ } finally {
1149
+ if (this.autoCompactInFlight === inFlight) this.autoCompactInFlight = void 0;
1150
+ }
1151
+ }
1152
+ /** Bundle of store operations the retention sweeps drive. */
1153
+ compactionCtx() {
1154
+ return {
1155
+ getAgentStatuses: () => this.getAgentStatuses(),
1156
+ readMessages: () => this.readMessages(),
1157
+ deleteMessages: (ids) => this.deleteMessages(ids)
1158
+ };
1159
+ }
1160
+ deleteMessages(ids) {
1161
+ if (ids.length === 0) return;
1162
+ this.transaction(() => deleteMessages(this.db, ids));
1163
+ }
1164
+ credentialGet(credentialId) {
1165
+ return credentialGet(this.db, credentialId);
1166
+ }
1167
+ credentialList() {
1168
+ return credentialList(this.db);
1169
+ }
1170
+ credentialStatusCounts() {
1171
+ return credentialStatusCounts(this.db);
1172
+ }
1173
+ credentialIssue(options) {
1174
+ return credentialIssue(this.db, (run) => this.transaction(run), options);
1175
+ }
1176
+ credentialVerify(credentialId, secret) {
1177
+ return credentialVerify(this.db, credentialId, secret);
1178
+ }
1179
+ credentialRevoke(credentialId, reason, by) {
1180
+ return credentialRevoke(this.db, credentialId, reason, by);
1181
+ }
1182
+ credentialRotate(credentialId, options) {
1183
+ return credentialRotate(this.db, (run) => this.transaction(run), credentialId, options);
1184
+ }
1185
+ startAutoCompactTimer(options) {
1186
+ if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
1187
+ const timer = setInterval(() => {
1188
+ void this.autoCompact(options).catch(() => {
1189
+ });
1190
+ }, options?.intervalMs ?? AUTO_COMPACT_INTERVAL_MS);
1191
+ timer.unref?.();
1192
+ this.autoCompactTimer = timer;
1193
+ return () => {
1194
+ clearInterval(timer);
1195
+ if (this.autoCompactTimer === timer) this.autoCompactTimer = null;
1196
+ };
1197
+ }
1198
+ async close() {
1199
+ if (this.closed) return;
1200
+ this.closed = true;
1201
+ if (this.autoCompactTimer !== null) clearInterval(this.autoCompactTimer);
1202
+ this.autoCompactTimer = null;
1203
+ this.db.close();
1204
+ }
1205
+ };
1206
+
1207
+ export {
1208
+ MAILBOX_PROJECT_SERVER_PROTOCOL_VERSION,
1209
+ MAILBOX_PROJECT_SERVER_MAX_FRAME_CHARS,
1210
+ isMailboxProjectServerClientMessage,
1211
+ isMailboxProjectServerMessage,
1212
+ encodeMailboxProjectServerMessage,
1213
+ mailboxProjectServerEndpoint,
1214
+ mailboxProjectServerMetadataPath,
1215
+ CREDENTIAL_VERIFY_MAX_FAILURES,
1216
+ CREDENTIAL_VERIFY_WINDOW_MS,
1217
+ CREDENTIAL_VERIFY_COOLDOWN_MS,
1218
+ CredentialVerifyThrottle,
1219
+ credentialVerifyThrottle,
1220
+ SQLITE_MAILBOX_FILE,
1221
+ SqliteMailbox
1222
+ };
1223
+ //# sourceMappingURL=chunk-5RP3QTRL.js.map