claudemesh-cli 1.32.1 → 1.34.0
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/entrypoints/cli.js +604 -398
- package/dist/entrypoints/cli.js.map +17 -16
- package/dist/entrypoints/mcp.js +2 -2
- package/dist/entrypoints/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/entrypoints/cli.js
CHANGED
|
@@ -104,7 +104,7 @@ __export(exports_urls, {
|
|
|
104
104
|
VERSION: () => VERSION,
|
|
105
105
|
URLS: () => URLS
|
|
106
106
|
});
|
|
107
|
-
var URLS, VERSION = "1.
|
|
107
|
+
var URLS, VERSION = "1.34.0", env;
|
|
108
108
|
var init_urls = __esm(() => {
|
|
109
109
|
URLS = {
|
|
110
110
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -4019,6 +4019,7 @@ __export(exports_daemon_route, {
|
|
|
4019
4019
|
tryListStateViaDaemon: () => tryListStateViaDaemon,
|
|
4020
4020
|
tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
|
|
4021
4021
|
tryListPeersViaDaemon: () => tryListPeersViaDaemon,
|
|
4022
|
+
tryListInboxViaDaemon: () => tryListInboxViaDaemon,
|
|
4022
4023
|
tryGetStateViaDaemon: () => tryGetStateViaDaemon,
|
|
4023
4024
|
tryGetSkillViaDaemon: () => tryGetSkillViaDaemon,
|
|
4024
4025
|
tryForgetViaDaemon: () => tryForgetViaDaemon
|
|
@@ -4049,6 +4050,22 @@ async function tryListPeersViaDaemon(mesh) {
|
|
|
4049
4050
|
return null;
|
|
4050
4051
|
}
|
|
4051
4052
|
}
|
|
4053
|
+
async function tryListInboxViaDaemon(mesh, limit = 100) {
|
|
4054
|
+
if (!await daemonReachable())
|
|
4055
|
+
return null;
|
|
4056
|
+
try {
|
|
4057
|
+
const path = `/v1/inbox${meshQuery(mesh)}${meshQuery(mesh) ? "&" : "?"}limit=${limit}`;
|
|
4058
|
+
const res = await ipc({ path, timeoutMs: 3000 });
|
|
4059
|
+
if (res.status !== 200)
|
|
4060
|
+
return null;
|
|
4061
|
+
return Array.isArray(res.body.items) ? res.body.items : [];
|
|
4062
|
+
} catch (err) {
|
|
4063
|
+
const msg = String(err);
|
|
4064
|
+
if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
|
|
4065
|
+
return null;
|
|
4066
|
+
return null;
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4052
4069
|
async function tryListSkillsViaDaemon(mesh) {
|
|
4053
4070
|
if (!await daemonReachable())
|
|
4054
4071
|
return null;
|
|
@@ -7948,8 +7965,15 @@ async function listPeersForMesh(slug) {
|
|
|
7948
7965
|
function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
|
|
7949
7966
|
const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
|
|
7950
7967
|
const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
|
|
7951
|
-
const
|
|
7952
|
-
|
|
7968
|
+
const peerRole = peer.peerRole ?? "session";
|
|
7969
|
+
const profileRole = peer.profile?.role?.trim() || undefined;
|
|
7970
|
+
return {
|
|
7971
|
+
...peer,
|
|
7972
|
+
...profileRole ? { role: profileRole } : {},
|
|
7973
|
+
peerRole,
|
|
7974
|
+
isSelf,
|
|
7975
|
+
isThisSession
|
|
7976
|
+
};
|
|
7953
7977
|
}
|
|
7954
7978
|
async function runPeers(flags) {
|
|
7955
7979
|
const config = readConfig();
|
|
@@ -7977,13 +8001,13 @@ async function runPeers(flags) {
|
|
|
7977
8001
|
allJson.push({ mesh: slug, peers: projected });
|
|
7978
8002
|
continue;
|
|
7979
8003
|
}
|
|
7980
|
-
const visible = flags.all ? peers : peers.filter((p) => p.
|
|
8004
|
+
const visible = flags.all ? peers : peers.filter((p) => p.peerRole !== "control-plane");
|
|
7981
8005
|
const sorted = visible.slice().sort((a, b) => {
|
|
7982
8006
|
const score = (p) => p.isThisSession ? 0 : p.isSelf ? 1 : 2;
|
|
7983
8007
|
return score(a) - score(b);
|
|
7984
8008
|
});
|
|
7985
|
-
const
|
|
7986
|
-
const header =
|
|
8009
|
+
const hiddenControlPlane = peers.length - visible.length;
|
|
8010
|
+
const header = hiddenControlPlane > 0 ? `peers on ${slug} (${sorted.length}, ${hiddenControlPlane} control-plane hidden — use --all)` : `peers on ${slug} (${sorted.length})`;
|
|
7987
8011
|
render.section(header);
|
|
7988
8012
|
if (sorted.length === 0) {
|
|
7989
8013
|
render.info(dim(" (no peers connected)"));
|
|
@@ -8220,38 +8244,47 @@ var exports_inbox = {};
|
|
|
8220
8244
|
__export(exports_inbox, {
|
|
8221
8245
|
runInbox: () => runInbox
|
|
8222
8246
|
});
|
|
8223
|
-
function formatMessage(msg) {
|
|
8224
|
-
const text = msg.
|
|
8225
|
-
const from = msg.
|
|
8226
|
-
const time = new Date(msg.
|
|
8227
|
-
const
|
|
8228
|
-
|
|
8247
|
+
function formatMessage(msg, includeMesh) {
|
|
8248
|
+
const text = msg.body ?? "[encrypted]";
|
|
8249
|
+
const from = msg.sender_name && msg.sender_name !== msg.sender_pubkey.slice(0, 8) ? `${msg.sender_name} (${msg.sender_pubkey.slice(0, 8)})` : msg.sender_pubkey.slice(0, 8);
|
|
8250
|
+
const time = new Date(msg.received_at).toLocaleTimeString();
|
|
8251
|
+
const topicTag = msg.topic ? ` (#${msg.topic})` : "";
|
|
8252
|
+
const meshTag = includeMesh ? ` [${msg.mesh}]` : "";
|
|
8253
|
+
return ` ${bold(from)} ${dim(`${meshTag}${topicTag} ${time}`)}
|
|
8229
8254
|
${text}`;
|
|
8230
8255
|
}
|
|
8231
8256
|
async function runInbox(flags) {
|
|
8232
|
-
const
|
|
8233
|
-
await
|
|
8234
|
-
|
|
8235
|
-
const messages = client.drainPushBuffer();
|
|
8257
|
+
const meshSlug = flags.mesh;
|
|
8258
|
+
const items = await tryListInboxViaDaemon(meshSlug, flags.limit ?? 100);
|
|
8259
|
+
if (items === null) {
|
|
8236
8260
|
if (flags.json) {
|
|
8237
|
-
process.stdout.write(
|
|
8261
|
+
process.stdout.write(`[]
|
|
8238
8262
|
`);
|
|
8239
8263
|
return;
|
|
8240
8264
|
}
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8265
|
+
render.info(dim("Daemon not reachable. Run `claudemesh daemon up` and retry."));
|
|
8266
|
+
return;
|
|
8267
|
+
}
|
|
8268
|
+
if (flags.json) {
|
|
8269
|
+
process.stdout.write(JSON.stringify(items, null, 2) + `
|
|
8270
|
+
`);
|
|
8271
|
+
return;
|
|
8272
|
+
}
|
|
8273
|
+
if (items.length === 0) {
|
|
8274
|
+
const scope = meshSlug ? `mesh "${meshSlug}"` : "any mesh";
|
|
8275
|
+
render.info(dim(`No messages on ${scope}.`));
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8278
|
+
const heading = meshSlug ? `inbox — ${meshSlug} (${items.length} message${items.length === 1 ? "" : "s"})` : `inbox (${items.length} message${items.length === 1 ? "" : "s"})`;
|
|
8279
|
+
render.section(heading);
|
|
8280
|
+
for (const msg of items) {
|
|
8281
|
+
process.stdout.write(formatMessage(msg, !meshSlug) + `
|
|
8248
8282
|
|
|
8249
8283
|
`);
|
|
8250
|
-
|
|
8251
|
-
});
|
|
8284
|
+
}
|
|
8252
8285
|
}
|
|
8253
8286
|
var init_inbox = __esm(() => {
|
|
8254
|
-
|
|
8287
|
+
init_daemon_route();
|
|
8255
8288
|
init_render();
|
|
8256
8289
|
init_styles();
|
|
8257
8290
|
});
|
|
@@ -9142,6 +9175,9 @@ function migrateOutbox(db) {
|
|
|
9142
9175
|
db.exec(`ALTER TABLE outbox ADD COLUMN ciphertext TEXT`);
|
|
9143
9176
|
if (!hasPriority)
|
|
9144
9177
|
db.exec(`ALTER TABLE outbox ADD COLUMN priority TEXT`);
|
|
9178
|
+
const hasSenderSessionPk = columnExists(db, "outbox", "sender_session_pubkey");
|
|
9179
|
+
if (!hasSenderSessionPk)
|
|
9180
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN sender_session_pubkey TEXT`);
|
|
9145
9181
|
}
|
|
9146
9182
|
function columnExists(db, table, column) {
|
|
9147
9183
|
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
@@ -9152,7 +9188,8 @@ function findByClientId(db, clientMessageId) {
|
|
|
9152
9188
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
9153
9189
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
9154
9190
|
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
9155
|
-
mesh, target_spec, nonce, ciphertext, priority
|
|
9191
|
+
mesh, target_spec, nonce, ciphertext, priority,
|
|
9192
|
+
sender_session_pubkey
|
|
9156
9193
|
FROM outbox WHERE client_message_id = ?
|
|
9157
9194
|
`).get(clientMessageId);
|
|
9158
9195
|
return row ?? null;
|
|
@@ -9162,9 +9199,10 @@ function insertPending(db, input) {
|
|
|
9162
9199
|
INSERT INTO outbox (
|
|
9163
9200
|
id, client_message_id, request_fingerprint, payload,
|
|
9164
9201
|
enqueued_at, attempts, next_attempt_at, status,
|
|
9165
|
-
mesh, target_spec, nonce, ciphertext, priority
|
|
9166
|
-
|
|
9167
|
-
|
|
9202
|
+
mesh, target_spec, nonce, ciphertext, priority,
|
|
9203
|
+
sender_session_pubkey
|
|
9204
|
+
) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending', ?, ?, ?, ?, ?, ?)
|
|
9205
|
+
`).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now, input.mesh ?? null, input.target_spec ?? null, input.nonce ?? null, input.ciphertext ?? null, input.priority ?? null, input.sender_session_pubkey ?? null);
|
|
9168
9206
|
}
|
|
9169
9207
|
function fingerprintsEqual(a, b) {
|
|
9170
9208
|
if (a.length !== b.length)
|
|
@@ -9185,7 +9223,8 @@ function listOutbox(db, p = {}) {
|
|
|
9185
9223
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
9186
9224
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
9187
9225
|
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
9188
|
-
mesh, target_spec, nonce, ciphertext, priority
|
|
9226
|
+
mesh, target_spec, nonce, ciphertext, priority,
|
|
9227
|
+
sender_session_pubkey
|
|
9189
9228
|
FROM outbox
|
|
9190
9229
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
9191
9230
|
ORDER BY enqueued_at DESC
|
|
@@ -9199,7 +9238,8 @@ function findById(db, id) {
|
|
|
9199
9238
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
9200
9239
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
9201
9240
|
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
9202
|
-
mesh, target_spec, nonce, ciphertext, priority
|
|
9241
|
+
mesh, target_spec, nonce, ciphertext, priority,
|
|
9242
|
+
sender_session_pubkey
|
|
9203
9243
|
FROM outbox WHERE id = ?
|
|
9204
9244
|
`).get(id) ?? null;
|
|
9205
9245
|
}
|
|
@@ -9347,7 +9387,8 @@ function acceptSend(req, deps) {
|
|
|
9347
9387
|
target_spec: req.target_spec,
|
|
9348
9388
|
nonce: req.nonce,
|
|
9349
9389
|
ciphertext: req.ciphertext,
|
|
9350
|
-
priority: req.priority
|
|
9390
|
+
priority: req.priority,
|
|
9391
|
+
sender_session_pubkey: req.sender_session_pubkey
|
|
9351
9392
|
});
|
|
9352
9393
|
return { kind: "accepted_pending", status: 202, client_message_id: clientId };
|
|
9353
9394
|
}
|
|
@@ -9446,6 +9487,10 @@ function listInbox(db, p) {
|
|
|
9446
9487
|
where.push("sender_pubkey = ?");
|
|
9447
9488
|
args.push(p.fromPubkey);
|
|
9448
9489
|
}
|
|
9490
|
+
if (p.mesh !== undefined) {
|
|
9491
|
+
where.push("mesh = ?");
|
|
9492
|
+
args.push(p.mesh);
|
|
9493
|
+
}
|
|
9449
9494
|
const sql = `
|
|
9450
9495
|
SELECT id, client_message_id, broker_message_id, mesh, topic,
|
|
9451
9496
|
sender_pubkey, sender_name, body, meta, received_at, reply_to_id
|
|
@@ -10197,10 +10242,12 @@ function makeHandler(opts) {
|
|
|
10197
10242
|
const fromPubkey = url.searchParams.get("from") ?? undefined;
|
|
10198
10243
|
const limitRaw = url.searchParams.get("limit");
|
|
10199
10244
|
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
|
|
10245
|
+
const meshFilter = meshFromCtx(url.searchParams.get("mesh")) ?? undefined;
|
|
10200
10246
|
const rows = listInbox(opts.inboxDb, {
|
|
10201
10247
|
since: Number.isFinite(since) ? since : undefined,
|
|
10202
10248
|
topic,
|
|
10203
10249
|
fromPubkey,
|
|
10250
|
+
...meshFilter ? { mesh: meshFilter } : {},
|
|
10204
10251
|
limit: Number.isFinite(limit ?? NaN) ? limit : undefined
|
|
10205
10252
|
});
|
|
10206
10253
|
respond(res, 200, {
|
|
@@ -10317,12 +10364,17 @@ function makeHandler(opts) {
|
|
|
10317
10364
|
respond(res, 404, { error: "mesh_not_attached", mesh: chosenSlug });
|
|
10318
10365
|
return;
|
|
10319
10366
|
}
|
|
10367
|
+
const senderSessionPubkey = session?.presence?.sessionPubkey;
|
|
10368
|
+
const senderSecretKey = session?.presence?.sessionSecretKey ?? meshCfg.secretKey;
|
|
10320
10369
|
try {
|
|
10321
|
-
const routed = await resolveAndEncrypt(parsed.req, broker,
|
|
10370
|
+
const routed = await resolveAndEncrypt(parsed.req, broker, senderSecretKey, chosenSlug);
|
|
10322
10371
|
parsed.req.target_spec = routed.target_spec;
|
|
10323
10372
|
parsed.req.ciphertext = routed.ciphertext;
|
|
10324
10373
|
parsed.req.nonce = routed.nonce;
|
|
10325
10374
|
parsed.req.mesh = routed.mesh;
|
|
10375
|
+
if (senderSessionPubkey) {
|
|
10376
|
+
parsed.req.sender_session_pubkey = senderSessionPubkey;
|
|
10377
|
+
}
|
|
10326
10378
|
} catch (e) {
|
|
10327
10379
|
respond(res, 502, { error: "route_failed", detail: String(e) });
|
|
10328
10380
|
return;
|
|
@@ -10450,9 +10502,7 @@ async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
|
|
|
10450
10502
|
return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
|
|
10451
10503
|
}
|
|
10452
10504
|
if (/^[0-9a-f]{64}$/i.test(to)) {
|
|
10453
|
-
const
|
|
10454
|
-
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
10455
|
-
const env3 = await encryptDirect2(req.message, to, senderSecret2);
|
|
10505
|
+
const env3 = await encryptDirect2(req.message, to, meshSecretKey);
|
|
10456
10506
|
return { target_spec: to, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
10457
10507
|
}
|
|
10458
10508
|
const peers = await broker.listPeers().catch(() => []);
|
|
@@ -10463,18 +10513,14 @@ async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
|
|
|
10463
10513
|
if (matches2.length > 1)
|
|
10464
10514
|
throw new Error(`prefix "${to}" is ambiguous (${matches2.length} matches)`);
|
|
10465
10515
|
const recipient2 = matches2[0].pubkey;
|
|
10466
|
-
const
|
|
10467
|
-
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
10468
|
-
const env3 = await encryptDirect2(req.message, recipient2, senderSecret2);
|
|
10516
|
+
const env3 = await encryptDirect2(req.message, recipient2, meshSecretKey);
|
|
10469
10517
|
return { target_spec: recipient2, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
10470
10518
|
}
|
|
10471
10519
|
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
10472
10520
|
if (!match)
|
|
10473
10521
|
throw new Error(`peer "${to}" not found`);
|
|
10474
10522
|
const recipient = match.pubkey;
|
|
10475
|
-
const
|
|
10476
|
-
const senderSecret = sessionKeys?.sessionSecretKey ?? meshSecretKey;
|
|
10477
|
-
const env2 = await encryptDirect2(req.message, recipient, senderSecret);
|
|
10523
|
+
const env2 = await encryptDirect2(req.message, recipient, meshSecretKey);
|
|
10478
10524
|
return { target_spec: recipient, ciphertext: env2.ciphertext, nonce: env2.nonce, mesh: meshSlug ?? "" };
|
|
10479
10525
|
}
|
|
10480
10526
|
function respond(res, status, body) {
|
|
@@ -10491,18 +10537,149 @@ var init_server = __esm(() => {
|
|
|
10491
10537
|
init_urls();
|
|
10492
10538
|
});
|
|
10493
10539
|
|
|
10494
|
-
// src/daemon/
|
|
10540
|
+
// src/daemon/ws-lifecycle.ts
|
|
10495
10541
|
import WebSocket2 from "ws";
|
|
10542
|
+
function connectWsWithBackoff(opts) {
|
|
10543
|
+
const helloAckTimeoutMs = opts.helloAckTimeoutMs ?? DEFAULT_HELLO_ACK_TIMEOUT_MS;
|
|
10544
|
+
const backoffCapsMs = opts.backoffCapsMs ?? DEFAULT_BACKOFF_CAPS_MS;
|
|
10545
|
+
const log2 = opts.log ?? defaultLog;
|
|
10546
|
+
let ws = null;
|
|
10547
|
+
let status = "closed";
|
|
10548
|
+
let closed = false;
|
|
10549
|
+
let reconnectAttempt = 0;
|
|
10550
|
+
let reconnectTimer = null;
|
|
10551
|
+
let helloTimer = null;
|
|
10552
|
+
const setStatus = (s) => {
|
|
10553
|
+
if (status === s)
|
|
10554
|
+
return;
|
|
10555
|
+
status = s;
|
|
10556
|
+
opts.onStatusChange?.(s);
|
|
10557
|
+
};
|
|
10558
|
+
const openOnce = () => {
|
|
10559
|
+
if (closed)
|
|
10560
|
+
return Promise.reject(new Error("client_closed"));
|
|
10561
|
+
setStatus("connecting");
|
|
10562
|
+
log2("info", "ws_open_attempt", { url: opts.url });
|
|
10563
|
+
const sock = new WebSocket2(opts.url);
|
|
10564
|
+
ws = sock;
|
|
10565
|
+
return new Promise((resolve, reject) => {
|
|
10566
|
+
sock.on("open", () => {
|
|
10567
|
+
log2("info", "ws_open_ok", { url: opts.url });
|
|
10568
|
+
(async () => {
|
|
10569
|
+
try {
|
|
10570
|
+
const hello = await opts.buildHello();
|
|
10571
|
+
sock.send(JSON.stringify(hello));
|
|
10572
|
+
log2("info", "ws_hello_sent", { url: opts.url });
|
|
10573
|
+
helloTimer = setTimeout(() => {
|
|
10574
|
+
log2("warn", "hello_ack_timeout", { url: opts.url });
|
|
10575
|
+
try {
|
|
10576
|
+
sock.close();
|
|
10577
|
+
} catch {}
|
|
10578
|
+
reject(new Error("hello_ack_timeout"));
|
|
10579
|
+
}, helloAckTimeoutMs);
|
|
10580
|
+
} catch (e) {
|
|
10581
|
+
log2("warn", "ws_build_hello_threw", { err: String(e) });
|
|
10582
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
10583
|
+
}
|
|
10584
|
+
})();
|
|
10585
|
+
});
|
|
10586
|
+
sock.on("message", (raw) => {
|
|
10587
|
+
let msg;
|
|
10588
|
+
try {
|
|
10589
|
+
msg = JSON.parse(raw.toString());
|
|
10590
|
+
} catch {
|
|
10591
|
+
return;
|
|
10592
|
+
}
|
|
10593
|
+
if (opts.isHelloAck(msg)) {
|
|
10594
|
+
if (helloTimer) {
|
|
10595
|
+
clearTimeout(helloTimer);
|
|
10596
|
+
helloTimer = null;
|
|
10597
|
+
}
|
|
10598
|
+
setStatus("open");
|
|
10599
|
+
reconnectAttempt = 0;
|
|
10600
|
+
log2("info", "ws_hello_acked", { url: opts.url });
|
|
10601
|
+
resolve();
|
|
10602
|
+
return;
|
|
10603
|
+
}
|
|
10604
|
+
opts.onMessage(msg);
|
|
10605
|
+
});
|
|
10606
|
+
sock.on("close", (code, reason) => {
|
|
10607
|
+
if (helloTimer) {
|
|
10608
|
+
clearTimeout(helloTimer);
|
|
10609
|
+
helloTimer = null;
|
|
10610
|
+
}
|
|
10611
|
+
const reasonStr = reason.toString("utf8");
|
|
10612
|
+
log2("warn", "ws_closed", { url: opts.url, code, reason: reasonStr, status });
|
|
10613
|
+
opts.onBeforeReconnect?.(code, reasonStr);
|
|
10614
|
+
if (closed) {
|
|
10615
|
+
setStatus("closed");
|
|
10616
|
+
return;
|
|
10617
|
+
}
|
|
10618
|
+
setStatus("reconnecting");
|
|
10619
|
+
const wait = backoffCapsMs[Math.min(reconnectAttempt, backoffCapsMs.length - 1)] ?? 30000;
|
|
10620
|
+
reconnectAttempt++;
|
|
10621
|
+
log2("info", "ws_reconnect_scheduled", { url: opts.url, wait_ms: wait, code, reason: reasonStr });
|
|
10622
|
+
reconnectTimer = setTimeout(() => openOnce().catch((err) => log2("warn", "ws_reconnect_failed", { url: opts.url, err: String(err) })), wait);
|
|
10623
|
+
if (status === "connecting" || status === "reconnecting") {
|
|
10624
|
+
reject(new Error(`closed_before_hello_${code}`));
|
|
10625
|
+
}
|
|
10626
|
+
});
|
|
10627
|
+
sock.on("error", (err) => log2("warn", "ws_error", { url: opts.url, err: err.message }));
|
|
10628
|
+
});
|
|
10629
|
+
};
|
|
10630
|
+
return openOnce().then(() => {
|
|
10631
|
+
const handle = {
|
|
10632
|
+
get status() {
|
|
10633
|
+
return status;
|
|
10634
|
+
},
|
|
10635
|
+
get ws() {
|
|
10636
|
+
return ws;
|
|
10637
|
+
},
|
|
10638
|
+
send(payload) {
|
|
10639
|
+
if (!ws || ws.readyState !== ws.OPEN) {
|
|
10640
|
+
throw new Error("ws_not_open");
|
|
10641
|
+
}
|
|
10642
|
+
ws.send(JSON.stringify(payload));
|
|
10643
|
+
},
|
|
10644
|
+
async close() {
|
|
10645
|
+
closed = true;
|
|
10646
|
+
if (reconnectTimer) {
|
|
10647
|
+
clearTimeout(reconnectTimer);
|
|
10648
|
+
reconnectTimer = null;
|
|
10649
|
+
}
|
|
10650
|
+
if (helloTimer) {
|
|
10651
|
+
clearTimeout(helloTimer);
|
|
10652
|
+
helloTimer = null;
|
|
10653
|
+
}
|
|
10654
|
+
try {
|
|
10655
|
+
ws?.close();
|
|
10656
|
+
} catch {}
|
|
10657
|
+
setStatus("closed");
|
|
10658
|
+
}
|
|
10659
|
+
};
|
|
10660
|
+
return handle;
|
|
10661
|
+
});
|
|
10662
|
+
}
|
|
10663
|
+
var DEFAULT_HELLO_ACK_TIMEOUT_MS = 5000, DEFAULT_BACKOFF_CAPS_MS, defaultLog = (level, msg, meta) => {
|
|
10664
|
+
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
10665
|
+
if (level === "info")
|
|
10666
|
+
process.stdout.write(line + `
|
|
10667
|
+
`);
|
|
10668
|
+
else
|
|
10669
|
+
process.stderr.write(line + `
|
|
10670
|
+
`);
|
|
10671
|
+
};
|
|
10672
|
+
var init_ws_lifecycle = __esm(() => {
|
|
10673
|
+
DEFAULT_BACKOFF_CAPS_MS = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
10674
|
+
});
|
|
10496
10675
|
|
|
10676
|
+
// src/daemon/broker.ts
|
|
10497
10677
|
class DaemonBrokerClient {
|
|
10498
10678
|
mesh;
|
|
10499
10679
|
opts;
|
|
10500
|
-
|
|
10680
|
+
lifecycle = null;
|
|
10501
10681
|
_status = "closed";
|
|
10502
10682
|
closed = false;
|
|
10503
|
-
reconnectAttempt = 0;
|
|
10504
|
-
reconnectTimer = null;
|
|
10505
|
-
helloTimer = null;
|
|
10506
10683
|
pendingAcks = new Map;
|
|
10507
10684
|
peerListResolvers = new Map;
|
|
10508
10685
|
skillListResolvers = new Map;
|
|
@@ -10511,8 +10688,6 @@ class DaemonBrokerClient {
|
|
|
10511
10688
|
stateListResolvers = new Map;
|
|
10512
10689
|
memoryStoreResolvers = new Map;
|
|
10513
10690
|
memoryRecallResolvers = new Map;
|
|
10514
|
-
sessionPubkey = null;
|
|
10515
|
-
sessionSecretKey = null;
|
|
10516
10691
|
opens = [];
|
|
10517
10692
|
reqCounter = 0;
|
|
10518
10693
|
constructor(mesh, opts = {}) {
|
|
@@ -10529,72 +10704,39 @@ class DaemonBrokerClient {
|
|
|
10529
10704
|
return this.mesh.meshId;
|
|
10530
10705
|
}
|
|
10531
10706
|
log = (level, msg, meta) => {
|
|
10532
|
-
(this.opts.log ??
|
|
10707
|
+
(this.opts.log ?? defaultLog2)(level, msg, { mesh: this.mesh.slug, ...meta });
|
|
10533
10708
|
};
|
|
10534
|
-
setConnStatus(s) {
|
|
10535
|
-
if (this._status === s)
|
|
10536
|
-
return;
|
|
10537
|
-
this._status = s;
|
|
10538
|
-
this.opts.onStatusChange?.(s);
|
|
10539
|
-
}
|
|
10540
10709
|
async connect() {
|
|
10541
10710
|
if (this.closed)
|
|
10542
10711
|
throw new Error("client_closed");
|
|
10543
10712
|
if (this._status === "connecting" || this._status === "open")
|
|
10544
10713
|
return;
|
|
10545
|
-
this.
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
timestamp: timestamp2,
|
|
10572
|
-
signature
|
|
10573
|
-
}));
|
|
10574
|
-
this.helloTimer = setTimeout(() => {
|
|
10575
|
-
this.log("warn", "broker_hello_ack_timeout");
|
|
10576
|
-
try {
|
|
10577
|
-
ws.close();
|
|
10578
|
-
} catch {}
|
|
10579
|
-
reject(new Error("hello_ack_timeout"));
|
|
10580
|
-
}, HELLO_ACK_TIMEOUT_MS2);
|
|
10581
|
-
} catch (e) {
|
|
10582
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
10583
|
-
}
|
|
10584
|
-
});
|
|
10585
|
-
ws.on("message", (raw) => {
|
|
10586
|
-
let msg;
|
|
10587
|
-
try {
|
|
10588
|
-
msg = JSON.parse(raw.toString());
|
|
10589
|
-
} catch {
|
|
10590
|
-
return;
|
|
10591
|
-
}
|
|
10592
|
-
if (msg.type === "hello_ack") {
|
|
10593
|
-
if (this.helloTimer)
|
|
10594
|
-
clearTimeout(this.helloTimer);
|
|
10595
|
-
this.helloTimer = null;
|
|
10596
|
-
this.setConnStatus("open");
|
|
10597
|
-
this.reconnectAttempt = 0;
|
|
10714
|
+
this.lifecycle = await connectWsWithBackoff({
|
|
10715
|
+
url: this.mesh.brokerUrl,
|
|
10716
|
+
buildHello: async () => {
|
|
10717
|
+
const { timestamp: timestamp2, signature } = await signHello(this.mesh.meshId, this.mesh.memberId, this.mesh.pubkey, this.mesh.secretKey);
|
|
10718
|
+
return {
|
|
10719
|
+
type: "hello",
|
|
10720
|
+
meshId: this.mesh.meshId,
|
|
10721
|
+
memberId: this.mesh.memberId,
|
|
10722
|
+
pubkey: this.mesh.pubkey,
|
|
10723
|
+
displayName: this.opts.displayName,
|
|
10724
|
+
sessionId: `daemon-${process.pid}`,
|
|
10725
|
+
pid: process.pid,
|
|
10726
|
+
cwd: process.cwd(),
|
|
10727
|
+
hostname: __require("node:os").hostname(),
|
|
10728
|
+
peerType: "ai",
|
|
10729
|
+
channel: "claudemesh-daemon",
|
|
10730
|
+
timestamp: timestamp2,
|
|
10731
|
+
signature
|
|
10732
|
+
};
|
|
10733
|
+
},
|
|
10734
|
+
isHelloAck: (msg) => msg.type === "hello_ack",
|
|
10735
|
+
onMessage: (msg) => this.handleMessage(msg),
|
|
10736
|
+
onStatusChange: (s) => {
|
|
10737
|
+
this._status = s;
|
|
10738
|
+
this.opts.onStatusChange?.(s);
|
|
10739
|
+
if (s === "open") {
|
|
10598
10740
|
const queued = this.opens.slice();
|
|
10599
10741
|
this.opens.length = 0;
|
|
10600
10742
|
for (const fn of queued) {
|
|
@@ -10604,123 +10746,121 @@ class DaemonBrokerClient {
|
|
|
10604
10746
|
this.log("warn", "open_handler_failed", { err: String(e) });
|
|
10605
10747
|
}
|
|
10606
10748
|
}
|
|
10607
|
-
resolve();
|
|
10608
|
-
return;
|
|
10609
|
-
}
|
|
10610
|
-
if (msg.type === "ack") {
|
|
10611
|
-
const id = String(msg.id ?? "");
|
|
10612
|
-
const ack = this.pendingAcks.get(id);
|
|
10613
|
-
if (ack) {
|
|
10614
|
-
this.pendingAcks.delete(id);
|
|
10615
|
-
clearTimeout(ack.timer);
|
|
10616
|
-
if (typeof msg.error === "string" && msg.error.length > 0) {
|
|
10617
|
-
ack.resolve({ ok: false, error: msg.error, permanent: classifyPermanent(msg.error) });
|
|
10618
|
-
} else {
|
|
10619
|
-
ack.resolve({ ok: true, messageId: String(msg.messageId ?? id) });
|
|
10620
|
-
}
|
|
10621
|
-
}
|
|
10622
|
-
return;
|
|
10623
|
-
}
|
|
10624
|
-
if (msg.type === "peers_list") {
|
|
10625
|
-
const reqId = String(msg._reqId ?? "");
|
|
10626
|
-
const pending = this.peerListResolvers.get(reqId);
|
|
10627
|
-
if (pending) {
|
|
10628
|
-
this.peerListResolvers.delete(reqId);
|
|
10629
|
-
clearTimeout(pending.timer);
|
|
10630
|
-
pending.resolve(Array.isArray(msg.peers) ? msg.peers : []);
|
|
10631
|
-
}
|
|
10632
|
-
return;
|
|
10633
|
-
}
|
|
10634
|
-
if (msg.type === "skill_list") {
|
|
10635
|
-
const reqId = String(msg._reqId ?? "");
|
|
10636
|
-
const pending = this.skillListResolvers.get(reqId);
|
|
10637
|
-
if (pending) {
|
|
10638
|
-
this.skillListResolvers.delete(reqId);
|
|
10639
|
-
clearTimeout(pending.timer);
|
|
10640
|
-
pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
|
|
10641
|
-
}
|
|
10642
|
-
return;
|
|
10643
|
-
}
|
|
10644
|
-
if (msg.type === "skill_data") {
|
|
10645
|
-
const reqId = String(msg._reqId ?? "");
|
|
10646
|
-
const pending = this.skillDataResolvers.get(reqId);
|
|
10647
|
-
if (pending) {
|
|
10648
|
-
this.skillDataResolvers.delete(reqId);
|
|
10649
|
-
clearTimeout(pending.timer);
|
|
10650
|
-
pending.resolve(msg.skill ?? null);
|
|
10651
|
-
}
|
|
10652
|
-
return;
|
|
10653
|
-
}
|
|
10654
|
-
if (msg.type === "state_value" || msg.type === "state_data") {
|
|
10655
|
-
const reqId = String(msg._reqId ?? "");
|
|
10656
|
-
const pending = this.stateGetResolvers.get(reqId);
|
|
10657
|
-
if (pending) {
|
|
10658
|
-
this.stateGetResolvers.delete(reqId);
|
|
10659
|
-
clearTimeout(pending.timer);
|
|
10660
|
-
pending.resolve(msg.state ?? msg.row ?? null);
|
|
10661
|
-
}
|
|
10662
|
-
return;
|
|
10663
10749
|
}
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
pending.resolve(typeof msg.memoryId === "string" ? msg.memoryId : null);
|
|
10681
|
-
}
|
|
10682
|
-
return;
|
|
10683
|
-
}
|
|
10684
|
-
if (msg.type === "memory_recall_result") {
|
|
10685
|
-
const reqId = String(msg._reqId ?? "");
|
|
10686
|
-
const pending = this.memoryRecallResolvers.get(reqId);
|
|
10687
|
-
if (pending) {
|
|
10688
|
-
this.memoryRecallResolvers.delete(reqId);
|
|
10689
|
-
clearTimeout(pending.timer);
|
|
10690
|
-
pending.resolve(Array.isArray(msg.matches) ? msg.matches : []);
|
|
10691
|
-
}
|
|
10692
|
-
return;
|
|
10693
|
-
}
|
|
10694
|
-
if (msg.type === "push" || msg.type === "inbound") {
|
|
10695
|
-
this.opts.onPush?.(msg);
|
|
10696
|
-
return;
|
|
10697
|
-
}
|
|
10698
|
-
});
|
|
10699
|
-
ws.on("close", (code, reason) => {
|
|
10700
|
-
if (this.helloTimer) {
|
|
10701
|
-
clearTimeout(this.helloTimer);
|
|
10702
|
-
this.helloTimer = null;
|
|
10703
|
-
}
|
|
10704
|
-
this.failPendingAcks(`broker_disconnected_${code}`);
|
|
10705
|
-
if (this.closed) {
|
|
10706
|
-
this.setConnStatus("closed");
|
|
10707
|
-
return;
|
|
10750
|
+
},
|
|
10751
|
+
onBeforeReconnect: (code) => this.failPendingAcks(`broker_disconnected_${code}`),
|
|
10752
|
+
log: (level, msg, meta) => this.log(level, `broker_${msg}`, meta)
|
|
10753
|
+
});
|
|
10754
|
+
}
|
|
10755
|
+
handleMessage(msg) {
|
|
10756
|
+
if (msg.type === "ack") {
|
|
10757
|
+
const id = String(msg.id ?? "");
|
|
10758
|
+
const ack = this.pendingAcks.get(id);
|
|
10759
|
+
if (ack) {
|
|
10760
|
+
this.pendingAcks.delete(id);
|
|
10761
|
+
clearTimeout(ack.timer);
|
|
10762
|
+
if (typeof msg.error === "string" && msg.error.length > 0) {
|
|
10763
|
+
ack.resolve({ ok: false, error: msg.error, permanent: classifyPermanent(msg.error) });
|
|
10764
|
+
} else {
|
|
10765
|
+
ack.resolve({ ok: true, messageId: String(msg.messageId ?? id) });
|
|
10708
10766
|
}
|
|
10709
|
-
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10714
|
-
|
|
10715
|
-
|
|
10767
|
+
}
|
|
10768
|
+
return;
|
|
10769
|
+
}
|
|
10770
|
+
if (msg.type === "peers_list") {
|
|
10771
|
+
const reqId = String(msg._reqId ?? "");
|
|
10772
|
+
const pending = this.peerListResolvers.get(reqId);
|
|
10773
|
+
if (pending) {
|
|
10774
|
+
this.peerListResolvers.delete(reqId);
|
|
10775
|
+
clearTimeout(pending.timer);
|
|
10776
|
+
pending.resolve(Array.isArray(msg.peers) ? msg.peers : []);
|
|
10777
|
+
}
|
|
10778
|
+
return;
|
|
10779
|
+
}
|
|
10780
|
+
if (msg.type === "skill_list") {
|
|
10781
|
+
const reqId = String(msg._reqId ?? "");
|
|
10782
|
+
const pending = this.skillListResolvers.get(reqId);
|
|
10783
|
+
if (pending) {
|
|
10784
|
+
this.skillListResolvers.delete(reqId);
|
|
10785
|
+
clearTimeout(pending.timer);
|
|
10786
|
+
pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
|
|
10787
|
+
}
|
|
10788
|
+
return;
|
|
10789
|
+
}
|
|
10790
|
+
if (msg.type === "skill_data") {
|
|
10791
|
+
const reqId = String(msg._reqId ?? "");
|
|
10792
|
+
const pending = this.skillDataResolvers.get(reqId);
|
|
10793
|
+
if (pending) {
|
|
10794
|
+
this.skillDataResolvers.delete(reqId);
|
|
10795
|
+
clearTimeout(pending.timer);
|
|
10796
|
+
pending.resolve(msg.skill ?? null);
|
|
10797
|
+
}
|
|
10798
|
+
return;
|
|
10799
|
+
}
|
|
10800
|
+
if (msg.type === "state_value" || msg.type === "state_data") {
|
|
10801
|
+
const reqId = String(msg._reqId ?? "");
|
|
10802
|
+
const pending = this.stateGetResolvers.get(reqId);
|
|
10803
|
+
if (pending) {
|
|
10804
|
+
this.stateGetResolvers.delete(reqId);
|
|
10805
|
+
clearTimeout(pending.timer);
|
|
10806
|
+
pending.resolve(msg.state ?? msg.row ?? null);
|
|
10807
|
+
}
|
|
10808
|
+
return;
|
|
10809
|
+
}
|
|
10810
|
+
if (msg.type === "state_list") {
|
|
10811
|
+
const reqId = String(msg._reqId ?? "");
|
|
10812
|
+
const pending = this.stateListResolvers.get(reqId);
|
|
10813
|
+
if (pending) {
|
|
10814
|
+
this.stateListResolvers.delete(reqId);
|
|
10815
|
+
clearTimeout(pending.timer);
|
|
10816
|
+
pending.resolve(Array.isArray(msg.entries) ? msg.entries : []);
|
|
10817
|
+
}
|
|
10818
|
+
return;
|
|
10819
|
+
}
|
|
10820
|
+
if (msg.type === "memory_stored") {
|
|
10821
|
+
const reqId = String(msg._reqId ?? "");
|
|
10822
|
+
const pending = this.memoryStoreResolvers.get(reqId);
|
|
10823
|
+
if (pending) {
|
|
10824
|
+
this.memoryStoreResolvers.delete(reqId);
|
|
10825
|
+
clearTimeout(pending.timer);
|
|
10826
|
+
pending.resolve(typeof msg.memoryId === "string" ? msg.memoryId : null);
|
|
10827
|
+
}
|
|
10828
|
+
return;
|
|
10829
|
+
}
|
|
10830
|
+
if (msg.type === "memory_recall_result") {
|
|
10831
|
+
const reqId = String(msg._reqId ?? "");
|
|
10832
|
+
const pending = this.memoryRecallResolvers.get(reqId);
|
|
10833
|
+
if (pending) {
|
|
10834
|
+
this.memoryRecallResolvers.delete(reqId);
|
|
10835
|
+
clearTimeout(pending.timer);
|
|
10836
|
+
pending.resolve(Array.isArray(msg.matches) ? msg.matches : []);
|
|
10837
|
+
}
|
|
10838
|
+
return;
|
|
10839
|
+
}
|
|
10840
|
+
if (msg.type === "push" || msg.type === "inbound") {
|
|
10841
|
+
this.opts.onPush?.(msg);
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
}
|
|
10845
|
+
isOpen() {
|
|
10846
|
+
const sock = this.lifecycle?.ws;
|
|
10847
|
+
return !!sock && sock.readyState === sock.OPEN;
|
|
10848
|
+
}
|
|
10849
|
+
sendClientAck(clientMessageId, brokerMessageId) {
|
|
10850
|
+
if (!this.isOpen())
|
|
10851
|
+
return;
|
|
10852
|
+
try {
|
|
10853
|
+
this.lifecycle.send({
|
|
10854
|
+
type: "client_ack",
|
|
10855
|
+
clientMessageId,
|
|
10856
|
+
...brokerMessageId ? { brokerMessageId } : {}
|
|
10716
10857
|
});
|
|
10717
|
-
|
|
10718
|
-
});
|
|
10858
|
+
} catch {}
|
|
10719
10859
|
}
|
|
10720
10860
|
send(req) {
|
|
10721
10861
|
return new Promise((resolve) => {
|
|
10722
10862
|
const dispatch = () => {
|
|
10723
|
-
if (!this.
|
|
10863
|
+
if (!this.isOpen()) {
|
|
10724
10864
|
resolve({ ok: false, error: "broker_not_open", permanent: false });
|
|
10725
10865
|
return;
|
|
10726
10866
|
}
|
|
@@ -10732,7 +10872,7 @@ class DaemonBrokerClient {
|
|
|
10732
10872
|
}, SEND_ACK_TIMEOUT_MS);
|
|
10733
10873
|
this.pendingAcks.set(id, { resolve, timer });
|
|
10734
10874
|
try {
|
|
10735
|
-
this.
|
|
10875
|
+
this.lifecycle.send({
|
|
10736
10876
|
type: "send",
|
|
10737
10877
|
id,
|
|
10738
10878
|
client_message_id: id,
|
|
@@ -10741,7 +10881,7 @@ class DaemonBrokerClient {
|
|
|
10741
10881
|
priority: req.priority,
|
|
10742
10882
|
nonce: req.nonce,
|
|
10743
10883
|
ciphertext: req.ciphertext
|
|
10744
|
-
})
|
|
10884
|
+
});
|
|
10745
10885
|
} catch (e) {
|
|
10746
10886
|
this.pendingAcks.delete(id);
|
|
10747
10887
|
clearTimeout(timer);
|
|
@@ -10755,7 +10895,7 @@ class DaemonBrokerClient {
|
|
|
10755
10895
|
});
|
|
10756
10896
|
}
|
|
10757
10897
|
async listPeers(timeoutMs = 5000) {
|
|
10758
|
-
if (this._status !== "open" || !this.
|
|
10898
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10759
10899
|
return [];
|
|
10760
10900
|
return new Promise((resolve) => {
|
|
10761
10901
|
const reqId = `pl-${++this.reqCounter}`;
|
|
@@ -10765,7 +10905,7 @@ class DaemonBrokerClient {
|
|
|
10765
10905
|
}, timeoutMs);
|
|
10766
10906
|
this.peerListResolvers.set(reqId, { resolve, timer });
|
|
10767
10907
|
try {
|
|
10768
|
-
this.
|
|
10908
|
+
this.lifecycle.send({ type: "list_peers", _reqId: reqId });
|
|
10769
10909
|
} catch {
|
|
10770
10910
|
this.peerListResolvers.delete(reqId);
|
|
10771
10911
|
clearTimeout(timer);
|
|
@@ -10774,7 +10914,7 @@ class DaemonBrokerClient {
|
|
|
10774
10914
|
});
|
|
10775
10915
|
}
|
|
10776
10916
|
async listSkills(query, timeoutMs = 5000) {
|
|
10777
|
-
if (this._status !== "open" || !this.
|
|
10917
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10778
10918
|
return [];
|
|
10779
10919
|
return new Promise((resolve) => {
|
|
10780
10920
|
const reqId = `sl-${++this.reqCounter}`;
|
|
@@ -10784,7 +10924,7 @@ class DaemonBrokerClient {
|
|
|
10784
10924
|
}, timeoutMs);
|
|
10785
10925
|
this.skillListResolvers.set(reqId, { resolve, timer });
|
|
10786
10926
|
try {
|
|
10787
|
-
this.
|
|
10927
|
+
this.lifecycle.send({ type: "list_skills", query, _reqId: reqId });
|
|
10788
10928
|
} catch {
|
|
10789
10929
|
this.skillListResolvers.delete(reqId);
|
|
10790
10930
|
clearTimeout(timer);
|
|
@@ -10793,7 +10933,7 @@ class DaemonBrokerClient {
|
|
|
10793
10933
|
});
|
|
10794
10934
|
}
|
|
10795
10935
|
async getSkill(name, timeoutMs = 5000) {
|
|
10796
|
-
if (this._status !== "open" || !this.
|
|
10936
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10797
10937
|
return null;
|
|
10798
10938
|
return new Promise((resolve) => {
|
|
10799
10939
|
const reqId = `sg-${++this.reqCounter}`;
|
|
@@ -10803,7 +10943,7 @@ class DaemonBrokerClient {
|
|
|
10803
10943
|
}, timeoutMs);
|
|
10804
10944
|
this.skillDataResolvers.set(reqId, { resolve, timer });
|
|
10805
10945
|
try {
|
|
10806
|
-
this.
|
|
10946
|
+
this.lifecycle.send({ type: "get_skill", name, _reqId: reqId });
|
|
10807
10947
|
} catch {
|
|
10808
10948
|
this.skillDataResolvers.delete(reqId);
|
|
10809
10949
|
clearTimeout(timer);
|
|
@@ -10812,7 +10952,7 @@ class DaemonBrokerClient {
|
|
|
10812
10952
|
});
|
|
10813
10953
|
}
|
|
10814
10954
|
async getState(key, timeoutMs = 5000) {
|
|
10815
|
-
if (this._status !== "open" || !this.
|
|
10955
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10816
10956
|
return null;
|
|
10817
10957
|
return new Promise((resolve) => {
|
|
10818
10958
|
const reqId = `sg-${++this.reqCounter}`;
|
|
@@ -10822,7 +10962,7 @@ class DaemonBrokerClient {
|
|
|
10822
10962
|
}, timeoutMs);
|
|
10823
10963
|
this.stateGetResolvers.set(reqId, { resolve, timer });
|
|
10824
10964
|
try {
|
|
10825
|
-
this.
|
|
10965
|
+
this.lifecycle.send({ type: "get_state", key, _reqId: reqId });
|
|
10826
10966
|
} catch {
|
|
10827
10967
|
this.stateGetResolvers.delete(reqId);
|
|
10828
10968
|
clearTimeout(timer);
|
|
@@ -10831,7 +10971,7 @@ class DaemonBrokerClient {
|
|
|
10831
10971
|
});
|
|
10832
10972
|
}
|
|
10833
10973
|
async listState(timeoutMs = 5000) {
|
|
10834
|
-
if (this._status !== "open" || !this.
|
|
10974
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10835
10975
|
return [];
|
|
10836
10976
|
return new Promise((resolve) => {
|
|
10837
10977
|
const reqId = `sl-${++this.reqCounter}`;
|
|
@@ -10841,7 +10981,7 @@ class DaemonBrokerClient {
|
|
|
10841
10981
|
}, timeoutMs);
|
|
10842
10982
|
this.stateListResolvers.set(reqId, { resolve, timer });
|
|
10843
10983
|
try {
|
|
10844
|
-
this.
|
|
10984
|
+
this.lifecycle.send({ type: "list_state", _reqId: reqId });
|
|
10845
10985
|
} catch {
|
|
10846
10986
|
this.stateListResolvers.delete(reqId);
|
|
10847
10987
|
clearTimeout(timer);
|
|
@@ -10850,14 +10990,14 @@ class DaemonBrokerClient {
|
|
|
10850
10990
|
});
|
|
10851
10991
|
}
|
|
10852
10992
|
setState(key, value) {
|
|
10853
|
-
if (this._status !== "open" || !this.
|
|
10993
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10854
10994
|
return;
|
|
10855
10995
|
try {
|
|
10856
|
-
this.
|
|
10996
|
+
this.lifecycle.send({ type: "set_state", key, value });
|
|
10857
10997
|
} catch {}
|
|
10858
10998
|
}
|
|
10859
10999
|
async remember(content, tags, timeoutMs = 5000) {
|
|
10860
|
-
if (this._status !== "open" || !this.
|
|
11000
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10861
11001
|
return null;
|
|
10862
11002
|
return new Promise((resolve) => {
|
|
10863
11003
|
const reqId = `mr-${++this.reqCounter}`;
|
|
@@ -10867,7 +11007,7 @@ class DaemonBrokerClient {
|
|
|
10867
11007
|
}, timeoutMs);
|
|
10868
11008
|
this.memoryStoreResolvers.set(reqId, { resolve, timer });
|
|
10869
11009
|
try {
|
|
10870
|
-
this.
|
|
11010
|
+
this.lifecycle.send({ type: "remember", content, tags, _reqId: reqId });
|
|
10871
11011
|
} catch {
|
|
10872
11012
|
this.memoryStoreResolvers.delete(reqId);
|
|
10873
11013
|
clearTimeout(timer);
|
|
@@ -10876,7 +11016,7 @@ class DaemonBrokerClient {
|
|
|
10876
11016
|
});
|
|
10877
11017
|
}
|
|
10878
11018
|
async recall(query, timeoutMs = 5000) {
|
|
10879
|
-
if (this._status !== "open" || !this.
|
|
11019
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10880
11020
|
return [];
|
|
10881
11021
|
return new Promise((resolve) => {
|
|
10882
11022
|
const reqId = `mc-${++this.reqCounter}`;
|
|
@@ -10886,7 +11026,7 @@ class DaemonBrokerClient {
|
|
|
10886
11026
|
}, timeoutMs);
|
|
10887
11027
|
this.memoryRecallResolvers.set(reqId, { resolve, timer });
|
|
10888
11028
|
try {
|
|
10889
|
-
this.
|
|
11029
|
+
this.lifecycle.send({ type: "recall", query, _reqId: reqId });
|
|
10890
11030
|
} catch {
|
|
10891
11031
|
this.memoryRecallResolvers.delete(reqId);
|
|
10892
11032
|
clearTimeout(timer);
|
|
@@ -10895,60 +11035,50 @@ class DaemonBrokerClient {
|
|
|
10895
11035
|
});
|
|
10896
11036
|
}
|
|
10897
11037
|
forget(memoryId) {
|
|
10898
|
-
if (this._status !== "open" || !this.
|
|
11038
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10899
11039
|
return;
|
|
10900
11040
|
try {
|
|
10901
|
-
this.
|
|
11041
|
+
this.lifecycle.send({ type: "forget", memoryId });
|
|
10902
11042
|
} catch {}
|
|
10903
11043
|
}
|
|
10904
11044
|
setProfile(profile) {
|
|
10905
|
-
if (this._status !== "open" || !this.
|
|
11045
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10906
11046
|
return;
|
|
10907
11047
|
try {
|
|
10908
|
-
this.
|
|
11048
|
+
this.lifecycle.send({ type: "set_profile", ...profile });
|
|
10909
11049
|
} catch {}
|
|
10910
11050
|
}
|
|
10911
11051
|
setSummary(summary) {
|
|
10912
|
-
if (this._status !== "open" || !this.
|
|
11052
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10913
11053
|
return;
|
|
10914
11054
|
try {
|
|
10915
|
-
this.
|
|
11055
|
+
this.lifecycle.send({ type: "set_summary", summary });
|
|
10916
11056
|
} catch {}
|
|
10917
11057
|
}
|
|
10918
11058
|
setStatus(status) {
|
|
10919
|
-
if (this._status !== "open" || !this.
|
|
11059
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10920
11060
|
return;
|
|
10921
11061
|
try {
|
|
10922
|
-
this.
|
|
11062
|
+
this.lifecycle.send({ type: "set_status", status });
|
|
10923
11063
|
} catch {}
|
|
10924
11064
|
}
|
|
10925
11065
|
setVisible(visible) {
|
|
10926
|
-
if (this._status !== "open" || !this.
|
|
11066
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
10927
11067
|
return;
|
|
10928
11068
|
try {
|
|
10929
|
-
this.
|
|
11069
|
+
this.lifecycle.send({ type: "set_visible", visible });
|
|
10930
11070
|
} catch {}
|
|
10931
11071
|
}
|
|
10932
11072
|
async close() {
|
|
10933
11073
|
this.closed = true;
|
|
10934
|
-
if (this.reconnectTimer) {
|
|
10935
|
-
clearTimeout(this.reconnectTimer);
|
|
10936
|
-
this.reconnectTimer = null;
|
|
10937
|
-
}
|
|
10938
|
-
if (this.helloTimer) {
|
|
10939
|
-
clearTimeout(this.helloTimer);
|
|
10940
|
-
this.helloTimer = null;
|
|
10941
|
-
}
|
|
10942
11074
|
this.failPendingAcks("daemon_shutdown");
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
return null;
|
|
10951
|
-
return { sessionPubkey: this.sessionPubkey, sessionSecretKey: this.sessionSecretKey };
|
|
11075
|
+
if (this.lifecycle) {
|
|
11076
|
+
try {
|
|
11077
|
+
await this.lifecycle.close();
|
|
11078
|
+
} catch {}
|
|
11079
|
+
this.lifecycle = null;
|
|
11080
|
+
}
|
|
11081
|
+
this._status = "closed";
|
|
10952
11082
|
}
|
|
10953
11083
|
failPendingAcks(reason) {
|
|
10954
11084
|
for (const [id, ack] of this.pendingAcks) {
|
|
@@ -10958,7 +11088,7 @@ class DaemonBrokerClient {
|
|
|
10958
11088
|
}
|
|
10959
11089
|
}
|
|
10960
11090
|
}
|
|
10961
|
-
function
|
|
11091
|
+
function defaultLog2(level, msg, meta) {
|
|
10962
11092
|
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
10963
11093
|
if (level === "info")
|
|
10964
11094
|
process.stdout.write(line + `
|
|
@@ -10970,24 +11100,26 @@ function defaultLog(level, msg, meta) {
|
|
|
10970
11100
|
function classifyPermanent(err) {
|
|
10971
11101
|
return /payload_too_large|forbidden|not_found|invalid|schema|auth|signature/i.test(err);
|
|
10972
11102
|
}
|
|
10973
|
-
var
|
|
11103
|
+
var SEND_ACK_TIMEOUT_MS = 15000;
|
|
10974
11104
|
var init_broker = __esm(() => {
|
|
10975
11105
|
init_hello_sig();
|
|
10976
|
-
|
|
11106
|
+
init_ws_lifecycle();
|
|
10977
11107
|
});
|
|
10978
11108
|
|
|
10979
11109
|
// src/daemon/session-broker.ts
|
|
10980
11110
|
import { hostname as osHostname } from "node:os";
|
|
10981
|
-
|
|
11111
|
+
function classifyPermanent2(error2) {
|
|
11112
|
+
return /unknown|invalid|forbidden|not_authorized|target_not_found/i.test(error2);
|
|
11113
|
+
}
|
|
10982
11114
|
|
|
10983
11115
|
class SessionBrokerClient {
|
|
10984
11116
|
opts;
|
|
10985
|
-
|
|
11117
|
+
lifecycle = null;
|
|
10986
11118
|
_status = "closed";
|
|
10987
11119
|
closed = false;
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
11120
|
+
brokerUnsupported = false;
|
|
11121
|
+
pendingAcks = new Map;
|
|
11122
|
+
opens = [];
|
|
10991
11123
|
constructor(opts) {
|
|
10992
11124
|
this.opts = opts;
|
|
10993
11125
|
}
|
|
@@ -11001,85 +11133,68 @@ class SessionBrokerClient {
|
|
|
11001
11133
|
return this.opts.sessionPubkey;
|
|
11002
11134
|
}
|
|
11003
11135
|
log = (level, msg, meta) => {
|
|
11004
|
-
(this.opts.log ??
|
|
11136
|
+
(this.opts.log ?? defaultLog3)(level, msg, {
|
|
11005
11137
|
mesh: this.opts.mesh.slug,
|
|
11006
11138
|
session_pubkey: this.opts.sessionPubkey.slice(0, 12),
|
|
11007
11139
|
...meta
|
|
11008
11140
|
});
|
|
11009
11141
|
};
|
|
11010
|
-
setStatus(s) {
|
|
11011
|
-
if (this._status === s)
|
|
11012
|
-
return;
|
|
11013
|
-
this._status = s;
|
|
11014
|
-
this.opts.onStatusChange?.(s);
|
|
11015
|
-
}
|
|
11016
11142
|
async connect() {
|
|
11017
11143
|
if (this.closed)
|
|
11018
11144
|
throw new Error("client_closed");
|
|
11019
11145
|
if (this._status === "connecting" || this._status === "open")
|
|
11020
11146
|
return;
|
|
11021
|
-
this.
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
this.helloTimer = setTimeout(() => {
|
|
11053
|
-
this.log("warn", "session_hello_ack_timeout");
|
|
11054
|
-
try {
|
|
11055
|
-
ws.close();
|
|
11056
|
-
} catch {}
|
|
11057
|
-
reject(new Error("session_hello_ack_timeout"));
|
|
11058
|
-
}, HELLO_ACK_TIMEOUT_MS3);
|
|
11059
|
-
} catch (e) {
|
|
11060
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
11061
|
-
}
|
|
11062
|
-
});
|
|
11063
|
-
ws.on("message", (raw) => {
|
|
11064
|
-
let msg;
|
|
11065
|
-
try {
|
|
11066
|
-
msg = JSON.parse(raw.toString());
|
|
11067
|
-
} catch {
|
|
11068
|
-
return;
|
|
11069
|
-
}
|
|
11070
|
-
if (msg.type === "hello_ack") {
|
|
11071
|
-
if (this.helloTimer)
|
|
11072
|
-
clearTimeout(this.helloTimer);
|
|
11073
|
-
this.helloTimer = null;
|
|
11074
|
-
this.setStatus("open");
|
|
11075
|
-
this.reconnectAttempt = 0;
|
|
11076
|
-
resolve();
|
|
11077
|
-
return;
|
|
11078
|
-
}
|
|
11147
|
+
this.lifecycle = await connectWsWithBackoff({
|
|
11148
|
+
url: this.opts.mesh.brokerUrl,
|
|
11149
|
+
buildHello: async () => {
|
|
11150
|
+
const { timestamp: timestamp2, signature } = await signSessionHello({
|
|
11151
|
+
meshId: this.opts.mesh.meshId,
|
|
11152
|
+
parentMemberPubkey: this.opts.mesh.pubkey,
|
|
11153
|
+
sessionPubkey: this.opts.sessionPubkey,
|
|
11154
|
+
sessionSecretKey: this.opts.sessionSecretKey
|
|
11155
|
+
});
|
|
11156
|
+
return {
|
|
11157
|
+
type: "session_hello",
|
|
11158
|
+
meshId: this.opts.mesh.meshId,
|
|
11159
|
+
parentMemberId: this.opts.mesh.memberId,
|
|
11160
|
+
parentMemberPubkey: this.opts.mesh.pubkey,
|
|
11161
|
+
sessionPubkey: this.opts.sessionPubkey,
|
|
11162
|
+
parentAttestation: this.opts.parentAttestation,
|
|
11163
|
+
displayName: this.opts.displayName,
|
|
11164
|
+
sessionId: this.opts.sessionId,
|
|
11165
|
+
pid: this.opts.pid,
|
|
11166
|
+
cwd: this.opts.cwd ?? process.cwd(),
|
|
11167
|
+
hostname: osHostname(),
|
|
11168
|
+
peerType: "ai",
|
|
11169
|
+
channel: "claudemesh-session",
|
|
11170
|
+
...this.opts.groups && this.opts.groups.length > 0 ? { groups: this.opts.groups } : {},
|
|
11171
|
+
...this.opts.role ? { role: this.opts.role } : {},
|
|
11172
|
+
timestamp: timestamp2,
|
|
11173
|
+
signature
|
|
11174
|
+
};
|
|
11175
|
+
},
|
|
11176
|
+
isHelloAck: (msg) => msg.type === "hello_ack",
|
|
11177
|
+
onMessage: (msg) => {
|
|
11079
11178
|
if (msg.type === "error") {
|
|
11080
11179
|
this.log("warn", "broker_error", { code: msg.code, message: msg.message });
|
|
11081
11180
|
if (msg.code === "unknown_message_type") {
|
|
11181
|
+
this.brokerUnsupported = true;
|
|
11082
11182
|
this.closed = true;
|
|
11183
|
+
this.lifecycle?.close();
|
|
11184
|
+
}
|
|
11185
|
+
return;
|
|
11186
|
+
}
|
|
11187
|
+
if (msg.type === "ack") {
|
|
11188
|
+
const id = String(msg.id ?? "");
|
|
11189
|
+
const ack = this.pendingAcks.get(id);
|
|
11190
|
+
if (ack) {
|
|
11191
|
+
this.pendingAcks.delete(id);
|
|
11192
|
+
clearTimeout(ack.timer);
|
|
11193
|
+
if (typeof msg.error === "string" && msg.error.length > 0) {
|
|
11194
|
+
ack.resolve({ ok: false, error: msg.error, permanent: classifyPermanent2(msg.error) });
|
|
11195
|
+
} else {
|
|
11196
|
+
ack.resolve({ ok: true, messageId: String(msg.messageId ?? id) });
|
|
11197
|
+
}
|
|
11083
11198
|
}
|
|
11084
11199
|
return;
|
|
11085
11200
|
}
|
|
@@ -11087,44 +11202,104 @@ class SessionBrokerClient {
|
|
|
11087
11202
|
this.opts.onPush?.(msg);
|
|
11088
11203
|
return;
|
|
11089
11204
|
}
|
|
11090
|
-
}
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
11094
|
-
|
|
11205
|
+
},
|
|
11206
|
+
onStatusChange: (s) => {
|
|
11207
|
+
this._status = s;
|
|
11208
|
+
this.opts.onStatusChange?.(s);
|
|
11209
|
+
if (s === "open") {
|
|
11210
|
+
const queued = this.opens.slice();
|
|
11211
|
+
this.opens.length = 0;
|
|
11212
|
+
for (const fn of queued) {
|
|
11213
|
+
try {
|
|
11214
|
+
fn();
|
|
11215
|
+
} catch (e) {
|
|
11216
|
+
this.log("warn", "session_open_handler_failed", { err: String(e) });
|
|
11217
|
+
}
|
|
11218
|
+
}
|
|
11219
|
+
} else if (s === "closed" || s === "reconnecting") {
|
|
11220
|
+
this.failPendingAcks(`session_ws_${s}`);
|
|
11095
11221
|
}
|
|
11096
|
-
|
|
11097
|
-
|
|
11222
|
+
},
|
|
11223
|
+
log: (level, msg, meta) => this.log(level, `session_broker_${msg}`, meta)
|
|
11224
|
+
});
|
|
11225
|
+
}
|
|
11226
|
+
sendClientAck(clientMessageId, brokerMessageId) {
|
|
11227
|
+
if (this._status !== "open" || !this.lifecycle)
|
|
11228
|
+
return;
|
|
11229
|
+
try {
|
|
11230
|
+
this.lifecycle.send({
|
|
11231
|
+
type: "client_ack",
|
|
11232
|
+
clientMessageId,
|
|
11233
|
+
...brokerMessageId ? { brokerMessageId } : {}
|
|
11234
|
+
});
|
|
11235
|
+
} catch {}
|
|
11236
|
+
}
|
|
11237
|
+
isOpen() {
|
|
11238
|
+
const sock = this.lifecycle?.ws;
|
|
11239
|
+
return !!sock && sock.readyState === sock.OPEN;
|
|
11240
|
+
}
|
|
11241
|
+
send(req) {
|
|
11242
|
+
return new Promise((resolve) => {
|
|
11243
|
+
const dispatch = () => {
|
|
11244
|
+
if (!this.isOpen() || !this.lifecycle) {
|
|
11245
|
+
resolve({ ok: false, error: "session_ws_not_open", permanent: false });
|
|
11098
11246
|
return;
|
|
11099
11247
|
}
|
|
11100
|
-
|
|
11101
|
-
const
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
|
|
11248
|
+
const id = req.client_message_id;
|
|
11249
|
+
const timer = setTimeout(() => {
|
|
11250
|
+
if (this.pendingAcks.delete(id)) {
|
|
11251
|
+
resolve({ ok: false, error: "ack_timeout", permanent: false });
|
|
11252
|
+
}
|
|
11253
|
+
}, SEND_ACK_TIMEOUT_MS2);
|
|
11254
|
+
this.pendingAcks.set(id, { resolve, timer });
|
|
11255
|
+
try {
|
|
11256
|
+
this.lifecycle.send({
|
|
11257
|
+
type: "send",
|
|
11258
|
+
id,
|
|
11259
|
+
client_message_id: id,
|
|
11260
|
+
request_fingerprint: req.request_fingerprint_hex,
|
|
11261
|
+
targetSpec: req.targetSpec,
|
|
11262
|
+
priority: req.priority,
|
|
11263
|
+
nonce: req.nonce,
|
|
11264
|
+
ciphertext: req.ciphertext
|
|
11265
|
+
});
|
|
11266
|
+
} catch (e) {
|
|
11267
|
+
this.pendingAcks.delete(id);
|
|
11268
|
+
clearTimeout(timer);
|
|
11269
|
+
resolve({ ok: false, error: `ws_write_failed: ${String(e)}`, permanent: false });
|
|
11270
|
+
}
|
|
11271
|
+
};
|
|
11272
|
+
if (this._status === "open")
|
|
11273
|
+
dispatch();
|
|
11274
|
+
else
|
|
11275
|
+
this.opens.push(dispatch);
|
|
11109
11276
|
});
|
|
11110
11277
|
}
|
|
11278
|
+
failPendingAcks(reason) {
|
|
11279
|
+
if (this.pendingAcks.size === 0)
|
|
11280
|
+
return;
|
|
11281
|
+
const entries = [...this.pendingAcks.entries()];
|
|
11282
|
+
this.pendingAcks.clear();
|
|
11283
|
+
for (const [, ack] of entries) {
|
|
11284
|
+
clearTimeout(ack.timer);
|
|
11285
|
+
ack.resolve({ ok: false, error: reason, permanent: false });
|
|
11286
|
+
}
|
|
11287
|
+
}
|
|
11111
11288
|
async close() {
|
|
11112
11289
|
this.closed = true;
|
|
11113
|
-
if (this.
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
clearTimeout(this.helloTimer);
|
|
11119
|
-
this.helloTimer = null;
|
|
11290
|
+
if (this.lifecycle) {
|
|
11291
|
+
try {
|
|
11292
|
+
await this.lifecycle.close();
|
|
11293
|
+
} catch {}
|
|
11294
|
+
this.lifecycle = null;
|
|
11120
11295
|
}
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
this.
|
|
11296
|
+
this._status = "closed";
|
|
11297
|
+
}
|
|
11298
|
+
get isBrokerUnsupported() {
|
|
11299
|
+
return this.brokerUnsupported;
|
|
11125
11300
|
}
|
|
11126
11301
|
}
|
|
11127
|
-
function
|
|
11302
|
+
function defaultLog3(level, msg, meta) {
|
|
11128
11303
|
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
11129
11304
|
if (level === "info")
|
|
11130
11305
|
process.stdout.write(line + `
|
|
@@ -11133,15 +11308,15 @@ function defaultLog2(level, msg, meta) {
|
|
|
11133
11308
|
process.stderr.write(line + `
|
|
11134
11309
|
`);
|
|
11135
11310
|
}
|
|
11136
|
-
var
|
|
11311
|
+
var SEND_ACK_TIMEOUT_MS2 = 15000;
|
|
11137
11312
|
var init_session_broker = __esm(() => {
|
|
11138
11313
|
init_session_hello_sig();
|
|
11139
|
-
|
|
11314
|
+
init_ws_lifecycle();
|
|
11140
11315
|
});
|
|
11141
11316
|
|
|
11142
11317
|
// src/daemon/drain.ts
|
|
11143
11318
|
function startDrainWorker(opts) {
|
|
11144
|
-
const log2 = opts.log ??
|
|
11319
|
+
const log2 = opts.log ?? defaultLog4;
|
|
11145
11320
|
let stopped = false;
|
|
11146
11321
|
let wakeResolve = null;
|
|
11147
11322
|
let wakePromise = new Promise((r) => {
|
|
@@ -11183,7 +11358,8 @@ async function drainOnce(opts, log2) {
|
|
|
11183
11358
|
const now = Date.now();
|
|
11184
11359
|
const rows = opts.db.prepare(`
|
|
11185
11360
|
SELECT id, client_message_id, request_fingerprint, payload, attempts,
|
|
11186
|
-
target_spec, nonce, ciphertext, priority, mesh
|
|
11361
|
+
target_spec, nonce, ciphertext, priority, mesh,
|
|
11362
|
+
sender_session_pubkey
|
|
11187
11363
|
FROM outbox
|
|
11188
11364
|
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
11189
11365
|
ORDER BY enqueued_at
|
|
@@ -11195,17 +11371,21 @@ async function drainOnce(opts, log2) {
|
|
|
11195
11371
|
if (markInflight(opts.db, row.id, now) === 0)
|
|
11196
11372
|
continue;
|
|
11197
11373
|
const fpHex = bufferToHex(row.request_fingerprint);
|
|
11198
|
-
let
|
|
11374
|
+
let daemonBroker;
|
|
11199
11375
|
if (row.mesh) {
|
|
11200
|
-
|
|
11376
|
+
daemonBroker = opts.brokers.get(row.mesh);
|
|
11201
11377
|
} else if (opts.brokers.size === 1) {
|
|
11202
|
-
|
|
11378
|
+
daemonBroker = opts.brokers.values().next().value;
|
|
11203
11379
|
}
|
|
11204
|
-
if (!
|
|
11380
|
+
if (!daemonBroker) {
|
|
11205
11381
|
log2("warn", "drain_no_broker_for_mesh", { id: row.id, mesh: row.mesh ?? "(null)" });
|
|
11206
11382
|
markDead(opts.db, row.id, `no_broker_for_mesh:${row.mesh ?? "null"}`);
|
|
11207
11383
|
continue;
|
|
11208
11384
|
}
|
|
11385
|
+
let sessionBroker;
|
|
11386
|
+
if (row.sender_session_pubkey && opts.getSessionBrokerByPubkey) {
|
|
11387
|
+
sessionBroker = opts.getSessionBrokerByPubkey(row.sender_session_pubkey);
|
|
11388
|
+
}
|
|
11209
11389
|
let targetSpec;
|
|
11210
11390
|
let nonce;
|
|
11211
11391
|
let ciphertext;
|
|
@@ -11221,16 +11401,29 @@ async function drainOnce(opts, log2) {
|
|
|
11221
11401
|
ciphertext = Buffer.from(row.payload).toString("base64");
|
|
11222
11402
|
priority = "next";
|
|
11223
11403
|
}
|
|
11404
|
+
const sendArgs = {
|
|
11405
|
+
targetSpec,
|
|
11406
|
+
priority,
|
|
11407
|
+
nonce,
|
|
11408
|
+
ciphertext,
|
|
11409
|
+
client_message_id: row.client_message_id,
|
|
11410
|
+
request_fingerprint_hex: fpHex
|
|
11411
|
+
};
|
|
11224
11412
|
let res;
|
|
11225
11413
|
try {
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
11414
|
+
if (row.sender_session_pubkey) {
|
|
11415
|
+
if (!sessionBroker || !sessionBroker.isOpen()) {
|
|
11416
|
+
log2("info", "drain_session_ws_not_ready", {
|
|
11417
|
+
id: row.id,
|
|
11418
|
+
session_pubkey: row.sender_session_pubkey.slice(0, 12)
|
|
11419
|
+
});
|
|
11420
|
+
backoffPending(opts.db, row.id, row.attempts + 1, "session_ws_not_open", "session_ws_not_open");
|
|
11421
|
+
continue;
|
|
11422
|
+
}
|
|
11423
|
+
res = await sessionBroker.send(sendArgs);
|
|
11424
|
+
} else {
|
|
11425
|
+
res = await daemonBroker.send(sendArgs);
|
|
11426
|
+
}
|
|
11234
11427
|
} catch (e) {
|
|
11235
11428
|
log2("warn", "drain_send_threw", { id: row.id, err: String(e) });
|
|
11236
11429
|
backoffPending(opts.db, row.id, row.attempts + 1, "exception", String(e));
|
|
@@ -11285,7 +11478,7 @@ async function randomNonce2() {
|
|
|
11285
11478
|
const { randomBytes: randomBytes6 } = await import("node:crypto");
|
|
11286
11479
|
return randomBytes6(24).toString("base64");
|
|
11287
11480
|
}
|
|
11288
|
-
function
|
|
11481
|
+
function defaultLog4(level, msg, meta) {
|
|
11289
11482
|
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
11290
11483
|
if (level === "info")
|
|
11291
11484
|
process.stdout.write(line + `
|
|
@@ -11342,6 +11535,7 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
11342
11535
|
received_at: Date.now(),
|
|
11343
11536
|
reply_to_id: replyToId
|
|
11344
11537
|
});
|
|
11538
|
+
ctx.ackClientMessage?.(clientMessageId, brokerMessageId);
|
|
11345
11539
|
if (!inserted)
|
|
11346
11540
|
return;
|
|
11347
11541
|
ctx.bus.publish("message", {
|
|
@@ -11612,13 +11806,12 @@ async function runDaemon(opts = {}) {
|
|
|
11612
11806
|
bus.publish("broker_status", { mesh: mesh.slug, status: s });
|
|
11613
11807
|
},
|
|
11614
11808
|
onPush: (m) => {
|
|
11615
|
-
const sessionKeys = broker.getSessionKeys();
|
|
11616
11809
|
handleBrokerPush(m, {
|
|
11617
11810
|
db: inboxDb,
|
|
11618
11811
|
bus,
|
|
11619
11812
|
meshSlug: mesh.slug,
|
|
11620
11813
|
recipientSecretKeyHex: mesh.secretKey,
|
|
11621
|
-
|
|
11814
|
+
ackClientMessage: (cmid, bmid) => broker.sendClientAck(cmid, bmid)
|
|
11622
11815
|
});
|
|
11623
11816
|
}
|
|
11624
11817
|
});
|
|
@@ -11626,9 +11819,14 @@ async function runDaemon(opts = {}) {
|
|
|
11626
11819
|
`));
|
|
11627
11820
|
brokers.set(mesh.slug, broker);
|
|
11628
11821
|
}
|
|
11629
|
-
let drain = null;
|
|
11630
|
-
drain = startDrainWorker({ db: outboxDb, brokers });
|
|
11631
11822
|
const sessionBrokers = new Map;
|
|
11823
|
+
const sessionBrokersByPubkey = new Map;
|
|
11824
|
+
let drain = null;
|
|
11825
|
+
drain = startDrainWorker({
|
|
11826
|
+
db: outboxDb,
|
|
11827
|
+
brokers,
|
|
11828
|
+
getSessionBrokerByPubkey: (pubkey) => sessionBrokersByPubkey.get(pubkey)
|
|
11829
|
+
});
|
|
11632
11830
|
setRegistryHooks({
|
|
11633
11831
|
onRegister: (info) => {
|
|
11634
11832
|
if (!info.presence)
|
|
@@ -11647,6 +11845,9 @@ async function runDaemon(opts = {}) {
|
|
|
11647
11845
|
const prior = sessionBrokers.get(info.token);
|
|
11648
11846
|
if (prior) {
|
|
11649
11847
|
sessionBrokers.delete(info.token);
|
|
11848
|
+
if (sessionBrokersByPubkey.get(prior.sessionPubkey) === prior) {
|
|
11849
|
+
sessionBrokersByPubkey.delete(prior.sessionPubkey);
|
|
11850
|
+
}
|
|
11650
11851
|
prior.close().catch(() => {});
|
|
11651
11852
|
}
|
|
11652
11853
|
const sessionSecretKeyHex = info.presence.sessionSecretKey;
|
|
@@ -11666,11 +11867,13 @@ async function runDaemon(opts = {}) {
|
|
|
11666
11867
|
bus,
|
|
11667
11868
|
meshSlug: meshConfig.slug,
|
|
11668
11869
|
recipientSecretKeyHex: meshConfig.secretKey,
|
|
11669
|
-
sessionSecretKeyHex
|
|
11870
|
+
sessionSecretKeyHex,
|
|
11871
|
+
ackClientMessage: (cmid, bmid) => client.sendClientAck(cmid, bmid)
|
|
11670
11872
|
});
|
|
11671
11873
|
}
|
|
11672
11874
|
});
|
|
11673
11875
|
sessionBrokers.set(info.token, client);
|
|
11876
|
+
sessionBrokersByPubkey.set(info.presence.sessionPubkey, client);
|
|
11674
11877
|
client.connect().catch((err) => process.stderr.write(JSON.stringify({
|
|
11675
11878
|
level: "warn",
|
|
11676
11879
|
msg: "session_broker_connect_failed",
|
|
@@ -11685,6 +11888,9 @@ async function runDaemon(opts = {}) {
|
|
|
11685
11888
|
if (!client)
|
|
11686
11889
|
return;
|
|
11687
11890
|
sessionBrokers.delete(info.token);
|
|
11891
|
+
if (sessionBrokersByPubkey.get(client.sessionPubkey) === client) {
|
|
11892
|
+
sessionBrokersByPubkey.delete(client.sessionPubkey);
|
|
11893
|
+
}
|
|
11688
11894
|
client.close().catch(() => {});
|
|
11689
11895
|
}
|
|
11690
11896
|
});
|
|
@@ -13076,8 +13282,8 @@ async function checkBrokerWs() {
|
|
|
13076
13282
|
const wsUrl = URLS.BROKER;
|
|
13077
13283
|
const start = Date.now();
|
|
13078
13284
|
try {
|
|
13079
|
-
const
|
|
13080
|
-
const ws = new
|
|
13285
|
+
const WebSocket3 = (await import("ws")).default;
|
|
13286
|
+
const ws = new WebSocket3(wsUrl);
|
|
13081
13287
|
const result = await new Promise((resolve2) => {
|
|
13082
13288
|
const timer = setTimeout(() => {
|
|
13083
13289
|
try {
|
|
@@ -13188,11 +13394,11 @@ __export(exports_status, {
|
|
|
13188
13394
|
runStatus: () => runStatus2
|
|
13189
13395
|
});
|
|
13190
13396
|
import { statSync as statSync4, existsSync as existsSync16 } from "node:fs";
|
|
13191
|
-
import
|
|
13397
|
+
import WebSocket3 from "ws";
|
|
13192
13398
|
async function probeBroker(url, timeoutMs = 4000) {
|
|
13193
13399
|
return new Promise((resolve2) => {
|
|
13194
13400
|
const started = Date.now();
|
|
13195
|
-
const ws = new
|
|
13401
|
+
const ws = new WebSocket3(url);
|
|
13196
13402
|
const timer = setTimeout(() => {
|
|
13197
13403
|
try {
|
|
13198
13404
|
ws.terminate();
|
|
@@ -16402,7 +16608,7 @@ var require_client = __commonJS((exports) => {
|
|
|
16402
16608
|
var ws_1 = __importDefault(__require("ws"));
|
|
16403
16609
|
var crypto_js_1 = require_crypto();
|
|
16404
16610
|
var MAX_QUEUED2 = 100;
|
|
16405
|
-
var
|
|
16611
|
+
var HELLO_ACK_TIMEOUT_MS2 = 5000;
|
|
16406
16612
|
var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
16407
16613
|
|
|
16408
16614
|
class MeshClient extends node_events_1.EventEmitter {
|
|
@@ -16467,7 +16673,7 @@ var require_client = __commonJS((exports) => {
|
|
|
16467
16673
|
this.debug("hello_ack timeout");
|
|
16468
16674
|
ws.close();
|
|
16469
16675
|
reject(new Error("hello_ack timeout"));
|
|
16470
|
-
},
|
|
16676
|
+
}, HELLO_ACK_TIMEOUT_MS2);
|
|
16471
16677
|
};
|
|
16472
16678
|
const onMessage = (raw) => {
|
|
16473
16679
|
let msg;
|
|
@@ -19479,7 +19685,7 @@ async function main() {
|
|
|
19479
19685
|
}
|
|
19480
19686
|
case "inbox": {
|
|
19481
19687
|
const { runInbox: runInbox2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
19482
|
-
await runInbox2({ json: !!flags.json });
|
|
19688
|
+
await runInbox2({ mesh: flags.mesh, json: !!flags.json, limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined });
|
|
19483
19689
|
break;
|
|
19484
19690
|
}
|
|
19485
19691
|
case "state": {
|
|
@@ -19734,7 +19940,7 @@ async function main() {
|
|
|
19734
19940
|
await runSend2({ mesh: flags.mesh, priority: flags.priority, json: !!flags.json, self: !!flags.self }, positionals[1] ?? "", positionals.slice(2).join(" "));
|
|
19735
19941
|
} else if (sub === "inbox") {
|
|
19736
19942
|
const { runInbox: runInbox2 } = await Promise.resolve().then(() => (init_inbox(), exports_inbox));
|
|
19737
|
-
await runInbox2({ json: !!flags.json });
|
|
19943
|
+
await runInbox2({ mesh: flags.mesh, json: !!flags.json, limit: typeof flags.limit === "number" ? flags.limit : typeof flags.limit === "string" ? Number.parseInt(flags.limit, 10) : undefined });
|
|
19738
19944
|
} else if (sub === "status") {
|
|
19739
19945
|
const { runMsgStatus: runMsgStatus2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
|
|
19740
19946
|
process.exit(await runMsgStatus2(positionals[1], { mesh: flags.mesh, json: !!flags.json }));
|
|
@@ -20287,4 +20493,4 @@ main().catch((err) => {
|
|
|
20287
20493
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
20288
20494
|
});
|
|
20289
20495
|
|
|
20290
|
-
//# debugId=
|
|
20496
|
+
//# debugId=55E9142D00D4EE3764756E2164756E21
|