@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.
- package/dist/chronicle/index.js +84 -6391
- package/dist/chronicle/project-server.js +84 -4868
- package/dist/chunk-2BUUIMQB.js +225 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2FQL4PDC.js +292 -0
- package/dist/chunk-2KKODIQU.js +2256 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3E244UDA.js +356 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-4LJJYBAU.js +388 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-4XV6IR37.js +187 -0
- package/dist/chunk-4ZHTKJHM.js +183 -0
- package/dist/chunk-52RTVH4M.js +361 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5DSMEVA4.js +931 -0
- package/dist/chunk-5RP3QTRL.js +1223 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-62V2AFVG.js +422 -0
- package/dist/chunk-65RDIOR2.js +2470 -0
- package/dist/chunk-6AKOTGLM.js +597 -0
- package/dist/chunk-7ZHZE5EO.js +28 -0
- package/dist/chunk-A455ATZB.js +140 -0
- package/dist/chunk-AD7ZFZCS.js +46 -0
- package/dist/chunk-AI4LLQ2U.js +856 -0
- package/dist/chunk-ASX6AW36.js +789 -0
- package/dist/chunk-AT4V4XGI.js +3522 -0
- package/dist/chunk-AWNQ6ED6.js +311 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-BAKVJLEV.js +417 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-CB2HFKTP.js +9 -0
- package/dist/chunk-CE4ZOCOI.js +1640 -0
- package/dist/chunk-CQQL4ZOQ.js +151 -0
- package/dist/chunk-DEIQMBD2.js +65 -0
- package/dist/chunk-E6I5RXVK.js +58 -0
- package/dist/chunk-EFEHDQGY.js +1589 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-EPC3GZD4.js +308 -0
- package/dist/chunk-FAYRX2AD.js +13 -0
- package/dist/chunk-FFSX2QDQ.js +362 -0
- package/dist/chunk-FHAMQS7Q.js +5856 -0
- package/dist/chunk-FIV4IJL7.js +2263 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FSWCWSTS.js +395 -0
- package/dist/chunk-FW5LDDMJ.js +16464 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5AAKEEZ.js +228 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GAZEWQEZ.js +231 -0
- package/dist/chunk-GFJOMSHG.js +125 -0
- package/dist/chunk-GFXZTHNI.js +158 -0
- package/dist/chunk-GM54GJBU.js +960 -0
- package/dist/chunk-GPQ3NJPR.js +900 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-H5E77KUJ.js +15 -0
- package/dist/chunk-H72H4XDU.js +1023 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-I45KAW2N.js +5020 -0
- package/dist/chunk-IQ35HMQL.js +1396 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-JJPMWPB6.js +109 -0
- package/dist/chunk-JTHKOYWU.js +663 -0
- package/dist/chunk-JVGPR3TS.js +365 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K3G6JBUC.js +5232 -0
- package/dist/chunk-K4YUNKU7.js +589 -0
- package/dist/chunk-KBZGE6LN.js +710 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KI4L2DUL.js +560 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KSJ5UTP7.js +154 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LZDDLNCZ.js +279 -0
- package/dist/chunk-M2WF6AXG.js +115 -0
- package/dist/chunk-M7BS2K6U.js +1 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NFAYHPRH.js +323 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-O74MJHXU.js +187 -0
- package/dist/chunk-OJZEX73F.js +112 -0
- package/dist/chunk-PAAWLLZX.js +74 -0
- package/dist/chunk-PFXFQQRT.js +339 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-QRTG4XTI.js +188 -0
- package/dist/chunk-QVOVMXJR.js +1351 -0
- package/dist/chunk-RCQYT2I4.js +757 -0
- package/dist/chunk-RD6DEE36.js +1178 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RPOA7IAE.js +150 -0
- package/dist/chunk-SN2DQRL6.js +1172 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SQP64RCS.js +5391 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-T3KGZBU5.js +3171 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TYW2GI5I.js +1077 -0
- package/dist/chunk-UIWONWVG.js +4959 -0
- package/dist/chunk-VB3H5WWB.js +151 -0
- package/dist/chunk-W4A6QA3D.js +258 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WEBMSQP2.js +123 -0
- package/dist/chunk-WOEZV3Q4.js +333 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-WZO23AWO.js +2779 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-XQ2XGIBF.js +2086 -0
- package/dist/chunk-Y2GE5TSI.js +355 -0
- package/dist/chunk-YBUJCZ57.js +351 -0
- package/dist/chunk-YNCVSFGF.js +149 -0
- package/dist/chunk-YUPMXOGP.js +282 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZEHUCJHY.js +129 -0
- package/dist/chunk-ZYU2YOJ6.js +1496 -0
- package/dist/chunk-ZZ4V3WIZ.js +163 -0
- package/dist/coordination/agents/index.js +145 -6260
- package/dist/coordination/index.js +365 -34324
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/provider-status-tracker.d.ts +4 -0
- package/dist/core/agent.d.ts +14 -1
- package/dist/core/index.js +267 -12480
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/design/index.js +50 -1087
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1599 -21888
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +112 -5139
- package/dist/hq/protocol.js +21 -668
- package/dist/index.js +1410 -92656
- package/dist/infrastructure/index.js +64 -4811
- package/dist/kernel/events/provider-events.d.ts +15 -0
- package/dist/kernel/index.js +14 -992
- package/dist/models/index.js +40 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/index.js +17 -279
- package/dist/plugin/index.js +584 -14835
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry-R23UBE7H.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/review-finding-store-DKAV3AS6.js +21 -0
- package/dist/review-report-store-OWY3EC3N.js +19 -0
- package/dist/security/index.js +114 -2202
- package/dist/session-catalog/index.js +36 -2103
- package/dist/session-catalog/project-server.js +42 -1435
- package/dist/skills/index.js +35 -1578
- package/dist/storage/index.js +182 -15600
- package/dist/tasking/index.js +10 -676
- package/dist/tools/index.js +52 -9590
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/errors.d.ts +18 -0
- package/dist/types/index.js +121 -1342
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/session-markers.js +8 -144
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +269 -5685
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/wiring/proxy-rewrite.d.ts +18 -0
- package/dist/wiring/proxy-rewrite.js +18 -121
- package/dist/worktree/index.js +6 -887
- 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
|