@cello-protocol/daemon 0.0.14 → 0.0.16
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/daemon.d.ts.map +1 -1
- package/dist/daemon.js +112 -14
- package/dist/daemon.js.map +1 -1
- package/dist/directory-bootstrap.d.ts +45 -0
- package/dist/directory-bootstrap.d.ts.map +1 -1
- package/dist/directory-bootstrap.js +63 -0
- package/dist/directory-bootstrap.js.map +1 -1
- package/dist/registration-context.d.ts +3 -0
- package/dist/registration-context.d.ts.map +1 -1
- package/dist/registration-context.js +8 -0
- package/dist/registration-context.js.map +1 -1
- package/dist/registration-manager.d.ts +11 -0
- package/dist/registration-manager.d.ts.map +1 -1
- package/dist/registration-manager.js +55 -12
- package/dist/registration-manager.js.map +1 -1
- package/dist/session-category.d.ts +19 -0
- package/dist/session-category.d.ts.map +1 -0
- package/dist/session-category.js +9 -0
- package/dist/session-category.js.map +1 -0
- package/dist/session-ceremony.d.ts +9 -1
- package/dist/session-ceremony.d.ts.map +1 -1
- package/dist/session-ceremony.js +34 -8
- package/dist/session-ceremony.js.map +1 -1
- package/dist/session-node-manager.d.ts +6 -0
- package/dist/session-node-manager.d.ts.map +1 -1
- package/dist/session-node-manager.js +12 -0
- package/dist/session-node-manager.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
package/dist/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EAQrB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAM/D,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoB7G,OAAO,KAAK,EAAE,kBAAkB,EAA+C,MAAM,yBAAyB,CAAC;AAM/G,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAqInF,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,SAAS,IAAI,oBAAoB,CAAC;IAClC;;;;OAIG;IACH,qBAAqB,IAAI,kBAAkB,CAAC;IAC5C;;;;;;;;OAQG;IACH,gBAAgB,IAAI,SAAS,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,IAAI,kBAAkB,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,IAAI,eAAe,CAAC;CACtC;AAyCD,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAyzH7E"}
|
package/dist/daemon.js
CHANGED
|
@@ -32,6 +32,7 @@ import { loadAgents } from "./agent-loader.js";
|
|
|
32
32
|
import { acquireLock, removeLock } from "./lock-file.js";
|
|
33
33
|
import { createIpcServer } from "./ipc-server.js";
|
|
34
34
|
import { SessionNodeManager } from "./session-node-manager.js";
|
|
35
|
+
import { classifySession } from "./session-category.js";
|
|
35
36
|
import { RetryQueue } from "./retry-queue.js";
|
|
36
37
|
import { NonceDedupStore } from "./nonce-dedup.js";
|
|
37
38
|
import { ContentParkClient } from "./content-park-client.js";
|
|
@@ -54,7 +55,7 @@ import { wireSessionCeremonyHandler, wireSessionOfferHandler, wireSealCeremonyHa
|
|
|
54
55
|
import { reDeriveFrontiers, findInflatedFrontier } from "./seal-frontier-verify.js";
|
|
55
56
|
import { LocalAutoNatStub } from "@cello-protocol/transport";
|
|
56
57
|
import { startHttpManifestPoll } from "./http-manifest-poll.js";
|
|
57
|
-
import { resolveDirectoryUrl } from "./directory-bootstrap.js";
|
|
58
|
+
import { resolveDirectoryUrl, manifestNodesToEndpoints } from "./directory-bootstrap.js";
|
|
58
59
|
/**
|
|
59
60
|
* M7-SESSION-001 (H-1): canonical byte encoding of a SEAL-INTERRUPTED leaf for
|
|
60
61
|
* Ed25519 signing/verification. Field order is fixed and deterministic. Both the
|
|
@@ -225,6 +226,10 @@ export async function startDaemon(config) {
|
|
|
225
226
|
// M7 Keystone: the version of the verified manifest, surfaced in ConnectResult.
|
|
226
227
|
// Stays 0 when no manifestProvider is configured (the M6 backward-compat path).
|
|
227
228
|
let verifiedManifestVersion = 0;
|
|
229
|
+
// DOD-MANIFEST-1: the consortium node set resolved to live directory endpoints from
|
|
230
|
+
// the VERIFIED manifest — the N-node roster a T-of-N ceremony (DOD-DKG-1) fans out to.
|
|
231
|
+
// Empty in the M6 backward-compat path (no manifest) or if no node resolves.
|
|
232
|
+
let consortiumEndpoints = [];
|
|
228
233
|
if (manifestProvider && manifestRootKeys && manifestThreshold !== undefined) {
|
|
229
234
|
try {
|
|
230
235
|
const manifest = await manifestProvider.loadAndVerify(manifestRootKeys, manifestThreshold);
|
|
@@ -261,6 +266,38 @@ export async function startDaemon(config) {
|
|
|
261
266
|
manifestVersion: manifest.version,
|
|
262
267
|
signerCount: manifest.signatures.length,
|
|
263
268
|
});
|
|
269
|
+
// DOD-MANIFEST-1: resolve the FULL verified node set to live directory
|
|
270
|
+
// endpoints (replaces the implicit single-endpoint assumption). This is the
|
|
271
|
+
// roster T-of-N ceremonies fan out to. Availability-aware — a node that is
|
|
272
|
+
// down is skipped, the rest still resolve (redundancy invariant: a ceremony
|
|
273
|
+
// needs only T of N). The resolved count is logged so an operator sees the
|
|
274
|
+
// real reachable consortium at startup; the ceremony layer (DOD-DKG-1)
|
|
275
|
+
// re-checks the threshold against this roster and never silently falls back
|
|
276
|
+
// to the single hardcoded endpoint for a missing/forged node.
|
|
277
|
+
consortiumEndpoints = await manifestNodesToEndpoints(manifest.nodes, { logger });
|
|
278
|
+
const declaredNodes = manifest.nodes.length;
|
|
279
|
+
const resolvedNodes = consortiumEndpoints.length;
|
|
280
|
+
// Carry the nodeId↔peerId pairing (not just peerIds) so a consumer/test can verify
|
|
281
|
+
// each manifest identity bound to the right live directory, not merely the set.
|
|
282
|
+
const consortiumLog = {
|
|
283
|
+
manifestVersion: manifest.version,
|
|
284
|
+
declaredNodes,
|
|
285
|
+
resolvedNodes,
|
|
286
|
+
nodes: consortiumEndpoints.map((e) => ({ nodeId: e.nodeId, peerId: e.peerId })),
|
|
287
|
+
};
|
|
288
|
+
// Degraded ≠ healthy: a verified manifest whose nodes don't all resolve must NOT
|
|
289
|
+
// log at the same severity as a full roster. Threshold-REFUSAL (refusing to run a
|
|
290
|
+
// ceremony below T) is DOD-DKG-1's gate; here we make the health signal loud so a
|
|
291
|
+
// shrunken/empty roster is visible and never buried at info.
|
|
292
|
+
if (resolvedNodes === 0) {
|
|
293
|
+
logger.error("directory.consortium.none", consortiumLog);
|
|
294
|
+
}
|
|
295
|
+
else if (resolvedNodes < declaredNodes) {
|
|
296
|
+
logger.warn("directory.consortium.partial", consortiumLog);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
logger.info("directory.consortium.resolved", consortiumLog);
|
|
300
|
+
}
|
|
264
301
|
}
|
|
265
302
|
}
|
|
266
303
|
}
|
|
@@ -285,6 +322,14 @@ export async function startDaemon(config) {
|
|
|
285
322
|
throw new Error("Manifest verification failed. The daemon cannot start with an unverified manifest when manifestProvider is configured. " +
|
|
286
323
|
"Check the logs for the specific failure reason (manifest_signature_invalid, manifest_expired, or manifest_version_rollback).");
|
|
287
324
|
}
|
|
325
|
+
// DOD-SIGN-1: resolve the consortium roster for a session/seal FROST ceremony (same source as
|
|
326
|
+
// registration's DOD-DKG-1 path — the verified manifest, re-resolved at ceremony time). NULL when
|
|
327
|
+
// no consortium manifest is configured → single-node ceremony (M6/M7 back-compat). Shared by all
|
|
328
|
+
// wireSessionCeremonyHandler / wireSealCeremonyHandler call sites below.
|
|
329
|
+
const resolveConsortiumRoster = async () => {
|
|
330
|
+
const m = manifestProvider?.getCurrentManifest();
|
|
331
|
+
return m ? await manifestNodesToEndpoints(m.nodes, { logger }) : null;
|
|
332
|
+
};
|
|
288
333
|
// CELLO-M7-CONN-001 (DOD-CONN-3): start the daemon-level HTTP manifest poll. It fetches
|
|
289
334
|
// ${directoryHttpUrl}/manifest, verifies the threshold signature against the locally-pinned
|
|
290
335
|
// root keys, applies anti-rollback + expiry, and adopts a newer manifest — independent of any
|
|
@@ -439,6 +484,7 @@ export async function startDaemon(config) {
|
|
|
439
484
|
agentPubkeyHex,
|
|
440
485
|
getNode: entry.getNode,
|
|
441
486
|
getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
|
|
487
|
+
getConsortiumEndpoints: resolveConsortiumRoster,
|
|
442
488
|
signaling: mgr,
|
|
443
489
|
logger,
|
|
444
490
|
});
|
|
@@ -449,6 +495,7 @@ export async function startDaemon(config) {
|
|
|
449
495
|
agentPubkeyHex,
|
|
450
496
|
getNode: entry.getNode,
|
|
451
497
|
getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
|
|
498
|
+
getConsortiumEndpoints: resolveConsortiumRoster,
|
|
452
499
|
signaling: mgr,
|
|
453
500
|
logger,
|
|
454
501
|
});
|
|
@@ -515,6 +562,7 @@ export async function startDaemon(config) {
|
|
|
515
562
|
agentPubkeyHex: agent.pubkey,
|
|
516
563
|
getNode: getDirectoryNode,
|
|
517
564
|
getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
|
|
565
|
+
getConsortiumEndpoints: resolveConsortiumRoster,
|
|
518
566
|
signaling: mgr,
|
|
519
567
|
logger,
|
|
520
568
|
});
|
|
@@ -524,6 +572,7 @@ export async function startDaemon(config) {
|
|
|
524
572
|
agentPubkeyHex: agent.pubkey,
|
|
525
573
|
getNode: getDirectoryNode,
|
|
526
574
|
getDirectoryEndpoint: async () => (directoryEndpointResolver ? (await directoryEndpointResolver()) ?? null : null),
|
|
575
|
+
getConsortiumEndpoints: resolveConsortiumRoster,
|
|
527
576
|
signaling: mgr,
|
|
528
577
|
logger,
|
|
529
578
|
});
|
|
@@ -911,9 +960,18 @@ export async function startDaemon(config) {
|
|
|
911
960
|
// array from SQLite. Shared by both getStatus() (daemon-wide) and the
|
|
912
961
|
// cello_status MCP handler (per-connection) so live MCP clients see the same
|
|
913
962
|
// interrupted sessions a CLI `cello status` would.
|
|
963
|
+
// `cello status` is a health snapshot, not a session archive. It surfaces ONLY genuinely
|
|
964
|
+
// RESUMABLE sessions (interrupted with messages exchanged) — never failed inits (interrupted,
|
|
965
|
+
// 0 messages — a dead handshake), which classify as "failed" and would otherwise accumulate
|
|
966
|
+
// unbounded and confuse. The list is also capped; the full, queryable history is `cello sessions`
|
|
967
|
+
// / cello_list_sessions (with filter + limit flags).
|
|
968
|
+
const STATUS_RESUMABLE_CAP = 10;
|
|
914
969
|
function buildInterruptedSessions() {
|
|
915
|
-
|
|
916
|
-
|
|
970
|
+
return sessionNodeManager
|
|
971
|
+
.getSessionsByStatus("interrupted")
|
|
972
|
+
.filter((row) => (row.message_count ?? 0) > 0) // resumable only — drop failed 0-message inits
|
|
973
|
+
.slice(0, STATUS_RESUMABLE_CAP)
|
|
974
|
+
.map((row) => ({
|
|
917
975
|
sessionId: row.session_id,
|
|
918
976
|
agentName: row.agent_name,
|
|
919
977
|
counterpartyPubkey: row.counterparty_pubkey,
|
|
@@ -1363,10 +1421,21 @@ export async function startDaemon(config) {
|
|
|
1363
1421
|
};
|
|
1364
1422
|
}
|
|
1365
1423
|
const persistence = getPersistence(name);
|
|
1424
|
+
// DOD-DKG-1: resolve the full consortium roster from the VERIFIED manifest so the DKG fans
|
|
1425
|
+
// across all N directory nodes. Re-resolved here (ceremony time) for fresh failover
|
|
1426
|
+
// coordinates. NULL when NO manifest is configured (→ single-node DKG, M6/M7 back-compat);
|
|
1427
|
+
// a (possibly EMPTY) array when a manifest IS configured. The null-vs-empty distinction is
|
|
1428
|
+
// load-bearing: an empty roster (consortium configured but unreachable) must REFUSE in
|
|
1429
|
+
// registration-manager, NOT downgrade to single-node (code-reviewer B1 / fallback-finder).
|
|
1430
|
+
const currentManifest = manifestProvider?.getCurrentManifest();
|
|
1431
|
+
const consortiumRoster = currentManifest
|
|
1432
|
+
? await manifestNodesToEndpoints(currentManifest.nodes, { logger })
|
|
1433
|
+
: null;
|
|
1366
1434
|
const ctx = new DaemonRegistrationContext({
|
|
1367
1435
|
signaling: agentSignaling,
|
|
1368
1436
|
getDirectoryNode: agentGetNode,
|
|
1369
1437
|
getDirectoryEndpoint: () => directoryEndpoint,
|
|
1438
|
+
getConsortiumEndpoints: () => consortiumRoster,
|
|
1370
1439
|
keyProvider,
|
|
1371
1440
|
persistence,
|
|
1372
1441
|
logger,
|
|
@@ -2123,27 +2192,56 @@ export async function startDaemon(config) {
|
|
|
2123
2192
|
// and the guidance strings that point here ("See cello_list_sessions") dead-end.
|
|
2124
2193
|
// Read from the persisted SQLite store, so it works after a daemon restart and
|
|
2125
2194
|
// from a fresh MCP connection (no in-memory session-node required).
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2195
|
+
// Classify → filter → cap a set of session rows into a SessionListResponse. Shared by the
|
|
2196
|
+
// per-agent MCP surface (cello_list_sessions) and the daemon-wide CLI surface (list_sessions),
|
|
2197
|
+
// so the operator-facing categories (open/closed/failed) and the default cap never drift.
|
|
2198
|
+
// Default filter = "open" (live/resumable only — the common case); default limit bounds the
|
|
2199
|
+
// response so a long-lived agent's history can't balloon it. `all` includes failed/closed.
|
|
2200
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
2201
|
+
const MAX_LIST_LIMIT = 500;
|
|
2202
|
+
function selectSessions(rows, params) {
|
|
2203
|
+
const rawFilter = typeof params?.filter === "string" ? params.filter : "open";
|
|
2204
|
+
const filter = rawFilter === "closed" || rawFilter === "failed" || rawFilter === "all" ? rawFilter : "open";
|
|
2205
|
+
let limit = Number(params?.limit);
|
|
2206
|
+
if (!Number.isFinite(limit) || limit < 1)
|
|
2207
|
+
limit = DEFAULT_LIST_LIMIT;
|
|
2208
|
+
limit = Math.min(Math.floor(limit), MAX_LIST_LIMIT);
|
|
2209
|
+
const classified = rows.map((row) => {
|
|
2210
|
+
const messageCount = row.message_count ?? 0;
|
|
2211
|
+
const category = classifySession(row.status, messageCount);
|
|
2212
|
+
return { row, messageCount, category };
|
|
2213
|
+
});
|
|
2214
|
+
const matched = filter === "all" ? classified : classified.filter((c) => c.category === filter);
|
|
2215
|
+
const sessions = matched.slice(0, limit).map(({ row, messageCount, category }) => ({
|
|
2135
2216
|
sessionId: row.session_id,
|
|
2136
2217
|
agentName: row.agent_name,
|
|
2137
2218
|
counterpartyPubkey: row.counterparty_pubkey,
|
|
2138
2219
|
status: row.status,
|
|
2139
|
-
|
|
2220
|
+
category,
|
|
2221
|
+
messageCount,
|
|
2140
2222
|
createdAt: new Date(row.created_at).toISOString(),
|
|
2141
2223
|
updatedAt: new Date(row.updated_at).toISOString(),
|
|
2142
2224
|
// interrupted_at is the canonical ISO interruption stamp; null for any
|
|
2143
2225
|
// session that was never interrupted (active/sealed).
|
|
2144
2226
|
interruptedAt: row.interrupted_at ?? null,
|
|
2145
2227
|
}));
|
|
2146
|
-
return { ok: true, sessions };
|
|
2228
|
+
return { ok: true, filter, limit, totalMatched: matched.length, sessions };
|
|
2229
|
+
}
|
|
2230
|
+
// cello_list_sessions (MCP, per current agent): the discovery surface for the by-id reads
|
|
2231
|
+
// (cello_get_transcript / cello_get_sealed_receipt). Accepts { filter?: open|closed|failed|all,
|
|
2232
|
+
// limit?: number } — defaults to open + DEFAULT_LIST_LIMIT so failed/dead handshakes don't drown
|
|
2233
|
+
// the live ones. Read from persisted SQLite, so it survives a daemon restart / fresh connection.
|
|
2234
|
+
handlers.set("cello_list_sessions", async (params, connectionId) => {
|
|
2235
|
+
const connState = perConnectionState.get(connectionId);
|
|
2236
|
+
if (!connState || !connState.currentAgent) {
|
|
2237
|
+
return NO_CURRENT_AGENT_RESPONSE;
|
|
2238
|
+
}
|
|
2239
|
+
return selectSessions(sessionNodeManager.getSessionsForAgent(connState.currentAgent), params);
|
|
2240
|
+
});
|
|
2241
|
+
// list_sessions (daemon-wide, for the `cello sessions` CLI which has no current agent): same
|
|
2242
|
+
// filter/limit semantics, across ALL agents.
|
|
2243
|
+
handlers.set("list_sessions", async (params) => {
|
|
2244
|
+
return selectSessions(sessionNodeManager.getAllSessions(), params);
|
|
2147
2245
|
});
|
|
2148
2246
|
// DAEMON-003 IPC handlers: queue_failed_send and check_nonce (AC-010)
|
|
2149
2247
|
handlers.set("queue_failed_send", async (params, _connectionId) => {
|