@kehto/services 0.21.2 → 0.22.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.
|
@@ -14,6 +14,8 @@ import '@kehto/runtime';
|
|
|
14
14
|
* random key, `p`-tagged to the server. Responses arrive the same way,
|
|
15
15
|
* `p`-tagged to the client, and are correlated by the inner JSON-RPC `id`.
|
|
16
16
|
* - Discovery reads kind-11316 (server) + kind-11317 (tools) announcements.
|
|
17
|
+
* - CEP-22 oversized transfers and CEP-41 open streams ride
|
|
18
|
+
* `notifications/progress` frames addressed by the request progressToken.
|
|
17
19
|
*
|
|
18
20
|
* Shipped on a separate entry (`@kehto/services/cvm-nostr-transport`) so the
|
|
19
21
|
* `nostr-tools` dependency stays out of the core `@kehto/services` bundle.
|
|
@@ -84,6 +86,10 @@ interface NostrCvmTransportOptions {
|
|
|
84
86
|
name: string;
|
|
85
87
|
version: string;
|
|
86
88
|
};
|
|
89
|
+
/** CEP-41: per-stream idle timeout before a keepalive ping is sent. */
|
|
90
|
+
streamIdleTimeoutMs?: number;
|
|
91
|
+
/** CEP-41: probe deadline waiting for a pong after a keepalive ping. */
|
|
92
|
+
streamProbeTimeoutMs?: number;
|
|
87
93
|
}
|
|
88
94
|
/**
|
|
89
95
|
* Create a Nostr-backed ContextVM transport.
|
|
@@ -7,15 +7,35 @@ var KIND_GIFT_WRAP_EPHEMERAL = 21059;
|
|
|
7
7
|
var KIND_GIFT_WRAP_REGULAR = 1059;
|
|
8
8
|
var KIND_ANNOUNCE_SERVER = 11316;
|
|
9
9
|
var KIND_ANNOUNCE_TOOLS = 11317;
|
|
10
|
+
var SUPPORT_ENCRYPTION = "support_encryption";
|
|
11
|
+
var SUPPORT_ENCRYPTION_EPHEMERAL = "support_encryption_ephemeral";
|
|
12
|
+
var SUPPORT_OVERSIZED_TRANSFER = "support_oversized_transfer";
|
|
13
|
+
var SUPPORT_OPEN_STREAM = "support_open_stream";
|
|
10
14
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
11
15
|
var DEFAULT_DISCOVER_TIMEOUT_MS = 6e3;
|
|
12
16
|
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
13
17
|
var SEEN_WRAP_LIMIT = 512;
|
|
18
|
+
var DIGEST_PREFIX = "sha256:";
|
|
19
|
+
var MAX_TRANSFER_CHUNKS = 1e4;
|
|
20
|
+
var MAX_TRANSFER_BYTES = 100 * 1024 * 1024;
|
|
21
|
+
var MAX_ACTIVE_TRANSFERS = 8;
|
|
22
|
+
var MAX_TRANSFER_LIFETIME_MS = 12e4;
|
|
23
|
+
var COMPLETION_MODE_RENDER = "render";
|
|
24
|
+
var DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
25
|
+
var DEFAULT_STREAM_PROBE_TIMEOUT_MS = 1e4;
|
|
26
|
+
var MAX_STREAM_BUFFERED_CHUNKS = 256;
|
|
27
|
+
var MAX_STREAM_BUFFERED_CODE_UNITS = 8 * 1024 * 1024;
|
|
28
|
+
var MAX_PING_NONCE_BYTES = 64;
|
|
14
29
|
var correlationCounter = 0;
|
|
15
30
|
function nextCorrelationId() {
|
|
16
31
|
correlationCounter += 1;
|
|
17
32
|
return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
18
33
|
}
|
|
34
|
+
var streamNonceCounter = 0;
|
|
35
|
+
function nextStreamNonce() {
|
|
36
|
+
streamNonceCounter += 1;
|
|
37
|
+
return `cvm-ping-${streamNonceCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;
|
|
38
|
+
}
|
|
19
39
|
function randomizedPastTimestamp() {
|
|
20
40
|
const jitter = Math.floor(Math.random() * 172800);
|
|
21
41
|
return Math.floor(Date.now() / 1e3) - jitter;
|
|
@@ -23,6 +43,19 @@ function randomizedPastTimestamp() {
|
|
|
23
43
|
function tagValue(tags, name) {
|
|
24
44
|
return tags.find((tag) => tag[0] === name)?.[1];
|
|
25
45
|
}
|
|
46
|
+
function progressTokenKey(token) {
|
|
47
|
+
if (typeof token === "string") return token.length > 0 ? `s:${token}` : null;
|
|
48
|
+
if (typeof token === "number" && Number.isFinite(token)) return `n:${token}`;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function isBoundedInteger(value, min, max) {
|
|
52
|
+
return typeof value === "number" && Number.isInteger(value) && value >= min && value <= max;
|
|
53
|
+
}
|
|
54
|
+
async function sha256Hex(text) {
|
|
55
|
+
const bytes = new TextEncoder().encode(text);
|
|
56
|
+
const buf = await crypto.subtle.digest("SHA-256", bytes);
|
|
57
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
58
|
+
}
|
|
26
59
|
function simplePoolAdapter(sp) {
|
|
27
60
|
return {
|
|
28
61
|
subscribe(relays, filter, params) {
|
|
@@ -47,11 +80,14 @@ function createNostrCvmTransport(options = {}) {
|
|
|
47
80
|
const clientSecretKey = options.clientSecretKey ?? generateSecretKey();
|
|
48
81
|
const clientPubkey = getPublicKey(clientSecretKey);
|
|
49
82
|
const clientInfo = options.clientInfo ?? { name: "kehto-cvm", version: "1.0.0" };
|
|
83
|
+
const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
|
|
84
|
+
const streamProbeTimeoutMs = options.streamProbeTimeoutMs ?? DEFAULT_STREAM_PROBE_TIMEOUT_MS;
|
|
50
85
|
const sessions = /* @__PURE__ */ new Map();
|
|
51
86
|
const pending = /* @__PURE__ */ new Map();
|
|
52
87
|
const eventHandlers = /* @__PURE__ */ new Set();
|
|
53
88
|
const relayRefcount = /* @__PURE__ */ new Map();
|
|
54
89
|
const seenWraps = /* @__PURE__ */ new Set();
|
|
90
|
+
const issuedTokens = /* @__PURE__ */ new Map();
|
|
55
91
|
let inbound = null;
|
|
56
92
|
let subscribedRelays = "";
|
|
57
93
|
function resolveRelays(server) {
|
|
@@ -113,23 +149,379 @@ function createNostrCvmTransport(options = {}) {
|
|
|
113
149
|
} catch {
|
|
114
150
|
return;
|
|
115
151
|
}
|
|
152
|
+
if (mcp.method === "notifications/progress") {
|
|
153
|
+
const params = mcp.params ?? {};
|
|
154
|
+
const cvm = params.cvm;
|
|
155
|
+
if (cvm && typeof cvm.type === "string") {
|
|
156
|
+
if (cvm.type === "oversized-transfer") {
|
|
157
|
+
void handleOversizedFrame(serverPubkey, params, cvm);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (cvm.type === "open-stream") {
|
|
161
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
162
|
+
if (!entry) return;
|
|
163
|
+
handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
routeMessage(serverPubkey, mcp);
|
|
169
|
+
}
|
|
170
|
+
async function publishStreamFrame(entry, stream, frame) {
|
|
171
|
+
const session = sessions.get(entry.serverPubkey);
|
|
172
|
+
if (!session) return;
|
|
173
|
+
stream.localProgress += 1;
|
|
174
|
+
await publishMcp(
|
|
175
|
+
{ pubkey: entry.serverPubkey },
|
|
176
|
+
session.relays,
|
|
177
|
+
{
|
|
178
|
+
jsonrpc: "2.0",
|
|
179
|
+
method: "notifications/progress",
|
|
180
|
+
params: {
|
|
181
|
+
progressToken: stream.token,
|
|
182
|
+
progress: stream.localProgress,
|
|
183
|
+
cvm: { type: "open-stream", ...frame }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function admitFrame(serverPubkey, token) {
|
|
189
|
+
const tokenKey = progressTokenKey(token);
|
|
190
|
+
if (!tokenKey) return null;
|
|
191
|
+
const correlationId = issuedTokens.get(`${serverPubkey}:${tokenKey}`);
|
|
192
|
+
if (!correlationId) return null;
|
|
193
|
+
const entry = pending.get(correlationId);
|
|
194
|
+
if (!entry || entry.serverPubkey !== serverPubkey) return null;
|
|
195
|
+
return entry;
|
|
196
|
+
}
|
|
197
|
+
function releaseTransfer(entry) {
|
|
198
|
+
if (!entry.oversized) return;
|
|
199
|
+
clearTimeout(entry.oversized.expiryTimer);
|
|
200
|
+
entry.oversized = void 0;
|
|
201
|
+
}
|
|
202
|
+
function releaseStream(entry) {
|
|
203
|
+
if (!entry.stream) return;
|
|
204
|
+
clearTimeout(entry.stream.idleTimer);
|
|
205
|
+
if (entry.stream.probeTimer) clearTimeout(entry.stream.probeTimer);
|
|
206
|
+
entry.stream = void 0;
|
|
207
|
+
}
|
|
208
|
+
function endStream(entry) {
|
|
209
|
+
releaseStream(entry);
|
|
210
|
+
entry.streamEnded = true;
|
|
211
|
+
}
|
|
212
|
+
function failStream(entry, reason) {
|
|
213
|
+
const stream = entry.stream;
|
|
214
|
+
if (!stream || entry.streamEnded) return;
|
|
215
|
+
endStream(entry);
|
|
216
|
+
void publishStreamFrame(entry, stream, { frameType: "abort", reason }).catch(() => {
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function resetStreamIdleTimer(entry, stream) {
|
|
220
|
+
clearTimeout(stream.idleTimer);
|
|
221
|
+
stream.idleTimer = setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs);
|
|
222
|
+
}
|
|
223
|
+
function onStreamIdle(entry) {
|
|
224
|
+
const stream = entry.stream;
|
|
225
|
+
if (!stream || entry.streamEnded) return;
|
|
226
|
+
if (stream.pendingNonce !== null) return;
|
|
227
|
+
const session = sessions.get(entry.serverPubkey);
|
|
228
|
+
if (!session) {
|
|
229
|
+
failStream(entry, "session closed");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
stream.pendingNonce = nextStreamNonce();
|
|
233
|
+
stream.probeTimer = setTimeout(() => failStream(entry, "probe timeout"), streamProbeTimeoutMs);
|
|
234
|
+
void publishStreamFrame(entry, stream, { frameType: "ping", nonce: stream.pendingNonce }).catch(() => failStream(entry, "ping publication failed"));
|
|
235
|
+
}
|
|
236
|
+
function releaseFrameState(entry) {
|
|
237
|
+
releaseTransfer(entry);
|
|
238
|
+
releaseStream(entry);
|
|
239
|
+
if (entry.tokenKey && issuedTokens.get(entry.tokenKey) === entry.correlationId) {
|
|
240
|
+
issuedTokens.delete(entry.tokenKey);
|
|
241
|
+
}
|
|
242
|
+
entry.tokenKey = void 0;
|
|
243
|
+
}
|
|
244
|
+
function settlePending(correlationId) {
|
|
245
|
+
const entry = pending.get(correlationId);
|
|
246
|
+
if (!entry) return null;
|
|
247
|
+
clearTimeout(entry.timer);
|
|
248
|
+
pending.delete(correlationId);
|
|
249
|
+
releaseFrameState(entry);
|
|
250
|
+
return entry;
|
|
251
|
+
}
|
|
252
|
+
function countActiveTransfers() {
|
|
253
|
+
let active = 0;
|
|
254
|
+
for (const entry of pending.values()) {
|
|
255
|
+
if (entry.oversized) active += 1;
|
|
256
|
+
}
|
|
257
|
+
return active;
|
|
258
|
+
}
|
|
259
|
+
async function handleOversizedFrame(serverPubkey, params, cvm) {
|
|
260
|
+
const entry = admitFrame(serverPubkey, params.progressToken);
|
|
261
|
+
if (!entry) return;
|
|
262
|
+
const progress = Number(params.progress ?? NaN);
|
|
263
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
264
|
+
if (cvm.frameType === "start") {
|
|
265
|
+
const digest = typeof cvm.digest === "string" ? cvm.digest : "";
|
|
266
|
+
const { totalBytes, totalChunks } = cvm;
|
|
267
|
+
if (cvm.completionMode !== COMPLETION_MODE_RENDER) return;
|
|
268
|
+
if (digest === "") return;
|
|
269
|
+
if (!isBoundedInteger(totalChunks, 1, MAX_TRANSFER_CHUNKS)) return;
|
|
270
|
+
if (!isBoundedInteger(totalBytes, 1, MAX_TRANSFER_BYTES)) return;
|
|
271
|
+
if (progress > Number.MAX_SAFE_INTEGER - totalChunks - 1) return;
|
|
272
|
+
if (!entry.oversized && countActiveTransfers() >= MAX_ACTIVE_TRANSFERS) return;
|
|
273
|
+
releaseTransfer(entry);
|
|
274
|
+
entry.oversized = {
|
|
275
|
+
digest,
|
|
276
|
+
totalBytes,
|
|
277
|
+
totalChunks,
|
|
278
|
+
startProgress: progress,
|
|
279
|
+
acceptProgress: null,
|
|
280
|
+
chunks: /* @__PURE__ */ new Map(),
|
|
281
|
+
codeUnitsReceived: 0,
|
|
282
|
+
// Hard expiry: progress frames extend the request deadline but never
|
|
283
|
+
// how long buffers may be held.
|
|
284
|
+
expiryTimer: setTimeout(() => releaseTransfer(entry), MAX_TRANSFER_LIFETIME_MS)
|
|
285
|
+
};
|
|
286
|
+
refreshPendingTimeout(entry);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const transfer = entry.oversized;
|
|
290
|
+
if (!transfer) return;
|
|
291
|
+
if (cvm.frameType === "accept") {
|
|
292
|
+
if (progress !== transfer.startProgress + 1) {
|
|
293
|
+
releaseTransfer(entry);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
transfer.acceptProgress = progress;
|
|
297
|
+
refreshPendingTimeout(entry);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (cvm.frameType === "chunk") {
|
|
301
|
+
if (typeof cvm.data !== "string" || progress <= transfer.startProgress) return;
|
|
302
|
+
if (transfer.chunks.has(progress)) return;
|
|
303
|
+
if (transfer.chunks.size >= transfer.totalChunks) {
|
|
304
|
+
releaseTransfer(entry);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
transfer.codeUnitsReceived += cvm.data.length;
|
|
308
|
+
if (transfer.codeUnitsReceived > MAX_TRANSFER_BYTES) {
|
|
309
|
+
releaseTransfer(entry);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
transfer.chunks.set(progress, cvm.data);
|
|
313
|
+
refreshPendingTimeout(entry);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (cvm.frameType === "abort") {
|
|
317
|
+
releaseTransfer(entry);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (cvm.frameType === "end") {
|
|
321
|
+
const message = await assembleTransfer(transfer, progress);
|
|
322
|
+
releaseTransfer(entry);
|
|
323
|
+
if (message) routeMessage(serverPubkey, message);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async function assembleTransfer(transfer, endProgress) {
|
|
328
|
+
const { startProgress, acceptProgress, totalChunks } = transfer;
|
|
329
|
+
const hasCompleteRange = (first) => {
|
|
330
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
331
|
+
if (!transfer.chunks.has(first + i)) return false;
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
};
|
|
335
|
+
const directStart = startProgress + 1;
|
|
336
|
+
const acceptGatedStart = startProgress + 2;
|
|
337
|
+
const firstProgress = hasCompleteRange(directStart) && endProgress === directStart + totalChunks ? directStart : acceptProgress === startProgress + 1 && hasCompleteRange(acceptGatedStart) && endProgress === acceptGatedStart + totalChunks ? acceptGatedStart : null;
|
|
338
|
+
if (firstProgress === null) return null;
|
|
339
|
+
let payload = "";
|
|
340
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
341
|
+
payload += transfer.chunks.get(firstProgress + i);
|
|
342
|
+
}
|
|
343
|
+
if (new TextEncoder().encode(payload).byteLength !== transfer.totalBytes) return null;
|
|
344
|
+
const expected = transfer.digest.startsWith(DIGEST_PREFIX) ? transfer.digest.slice(DIGEST_PREFIX.length) : transfer.digest;
|
|
345
|
+
if (await sha256Hex(payload) !== expected) return null;
|
|
346
|
+
try {
|
|
347
|
+
return JSON.parse(payload);
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp) {
|
|
353
|
+
const progress = Number(params.progress ?? NaN);
|
|
354
|
+
if (!Number.isSafeInteger(progress) || progress < 0) return;
|
|
355
|
+
if (entry.streamEnded) return;
|
|
356
|
+
const stream = entry.stream;
|
|
357
|
+
if (!stream) {
|
|
358
|
+
if (cvm.frameType !== "start") return;
|
|
359
|
+
entry.stream = {
|
|
360
|
+
token: params.progressToken,
|
|
361
|
+
lastProgress: progress,
|
|
362
|
+
localProgress: 0,
|
|
363
|
+
nextChunkIndex: 0,
|
|
364
|
+
outOfOrder: /* @__PURE__ */ new Map(),
|
|
365
|
+
outOfOrderCodeUnits: 0,
|
|
366
|
+
pendingNonce: null,
|
|
367
|
+
idleTimer: setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs),
|
|
368
|
+
probeTimer: null
|
|
369
|
+
};
|
|
370
|
+
refreshPendingTimeout(entry);
|
|
371
|
+
routeEvent(serverPubkey, mcp);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const admitOrderedActivity = () => {
|
|
375
|
+
if (progress <= stream.lastProgress) {
|
|
376
|
+
failStream(entry, "non-monotonic progress");
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
stream.lastProgress = progress;
|
|
380
|
+
resetStreamIdleTimer(entry, stream);
|
|
381
|
+
refreshPendingTimeout(entry);
|
|
382
|
+
return true;
|
|
383
|
+
};
|
|
384
|
+
switch (cvm.frameType) {
|
|
385
|
+
case "start":
|
|
386
|
+
failStream(entry, "duplicate start");
|
|
387
|
+
return;
|
|
388
|
+
case "accept":
|
|
389
|
+
if (!admitOrderedActivity()) return;
|
|
390
|
+
routeEvent(serverPubkey, mcp);
|
|
391
|
+
return;
|
|
392
|
+
case "chunk": {
|
|
393
|
+
if (typeof cvm.data !== "string" || !isBoundedInteger(cvm.chunkIndex, 0, Number.MAX_SAFE_INTEGER)) {
|
|
394
|
+
failStream(entry, "malformed chunk");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const index = cvm.chunkIndex;
|
|
398
|
+
if (index < stream.nextChunkIndex) return;
|
|
399
|
+
if (index > stream.nextChunkIndex) {
|
|
400
|
+
const existing = stream.outOfOrder.get(index);
|
|
401
|
+
if (existing) {
|
|
402
|
+
if (existing.progress !== progress || JSON.stringify(existing.message) !== JSON.stringify(mcp)) {
|
|
403
|
+
failStream(entry, "conflicting duplicate chunk");
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (progress <= stream.lastProgress || stream.outOfOrder.size >= MAX_STREAM_BUFFERED_CHUNKS || stream.outOfOrderCodeUnits + cvm.data.length > MAX_STREAM_BUFFERED_CODE_UNITS) {
|
|
408
|
+
failStream(entry, progress <= stream.lastProgress ? "non-monotonic progress" : "gap buffer exhausted");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
stream.outOfOrder.set(index, { message: mcp, progress, codeUnits: cvm.data.length });
|
|
412
|
+
stream.outOfOrderCodeUnits += cvm.data.length;
|
|
413
|
+
resetStreamIdleTimer(entry, stream);
|
|
414
|
+
refreshPendingTimeout(entry);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
const ready = [
|
|
418
|
+
{ message: mcp, progress, codeUnits: 0 }
|
|
419
|
+
];
|
|
420
|
+
let nextIndex = index + 1;
|
|
421
|
+
while (stream.outOfOrder.has(nextIndex)) {
|
|
422
|
+
ready.push(stream.outOfOrder.get(nextIndex));
|
|
423
|
+
nextIndex += 1;
|
|
424
|
+
}
|
|
425
|
+
let previousProgress = stream.lastProgress;
|
|
426
|
+
for (const item of ready) {
|
|
427
|
+
if (item.progress <= previousProgress) {
|
|
428
|
+
failStream(entry, "non-monotonic chunk order");
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
previousProgress = item.progress;
|
|
432
|
+
}
|
|
433
|
+
for (let bufferedIndex = index + 1; bufferedIndex < nextIndex; bufferedIndex++) {
|
|
434
|
+
const buffered = stream.outOfOrder.get(bufferedIndex);
|
|
435
|
+
stream.outOfOrder.delete(bufferedIndex);
|
|
436
|
+
stream.outOfOrderCodeUnits -= buffered.codeUnits;
|
|
437
|
+
}
|
|
438
|
+
for (const item of ready) {
|
|
439
|
+
routeEvent(serverPubkey, item.message);
|
|
440
|
+
stream.nextChunkIndex += 1;
|
|
441
|
+
}
|
|
442
|
+
stream.lastProgress = previousProgress;
|
|
443
|
+
resetStreamIdleTimer(entry, stream);
|
|
444
|
+
refreshPendingTimeout(entry);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
case "ping": {
|
|
448
|
+
if (typeof cvm.nonce !== "string") return;
|
|
449
|
+
if (new TextEncoder().encode(cvm.nonce).byteLength > MAX_PING_NONCE_BYTES) return;
|
|
450
|
+
if (!admitOrderedActivity()) return;
|
|
451
|
+
void publishStreamFrame(entry, stream, { frameType: "pong", nonce: cvm.nonce }).catch(() => {
|
|
452
|
+
failStream(entry, "pong publication failed");
|
|
453
|
+
});
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
case "pong": {
|
|
457
|
+
if (stream.pendingNonce === null || cvm.nonce !== stream.pendingNonce) return;
|
|
458
|
+
if (!admitOrderedActivity()) return;
|
|
459
|
+
stream.pendingNonce = null;
|
|
460
|
+
if (stream.probeTimer) clearTimeout(stream.probeTimer);
|
|
461
|
+
stream.probeTimer = null;
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
case "close": {
|
|
465
|
+
if (cvm.lastChunkIndex !== void 0) {
|
|
466
|
+
const bound = cvm.lastChunkIndex;
|
|
467
|
+
const complete = isBoundedInteger(bound, 0, Number.MAX_SAFE_INTEGER) && stream.nextChunkIndex === bound + 1 && stream.outOfOrder.size === 0;
|
|
468
|
+
if (!complete) {
|
|
469
|
+
failStream(entry, "close with unresolved chunk gaps");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (!admitOrderedActivity()) return;
|
|
474
|
+
routeEvent(serverPubkey, mcp);
|
|
475
|
+
endStream(entry);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
case "abort":
|
|
479
|
+
if (!admitOrderedActivity()) return;
|
|
480
|
+
routeEvent(serverPubkey, mcp);
|
|
481
|
+
endStream(entry);
|
|
482
|
+
return;
|
|
483
|
+
default:
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function refreshPendingTimeout(entry) {
|
|
488
|
+
clearTimeout(entry.timer);
|
|
489
|
+
entry.timer = setTimeout(() => {
|
|
490
|
+
const settled = settlePending(entry.correlationId);
|
|
491
|
+
settled?.reject(new Error("relay timeout"));
|
|
492
|
+
}, entry.timeoutMs);
|
|
493
|
+
}
|
|
494
|
+
function routeMessage(serverPubkey, mcp) {
|
|
116
495
|
const id = mcp.id;
|
|
117
496
|
if (id != null && pending.has(String(id))) {
|
|
118
497
|
const entry = pending.get(String(id));
|
|
119
498
|
if (entry.serverPubkey !== serverPubkey) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
entry.resolve({ ...mcp, id: entry.originalId });
|
|
499
|
+
const settled = settlePending(String(id));
|
|
500
|
+
settled?.resolve({ ...mcp, id: entry.originalId });
|
|
123
501
|
return;
|
|
124
502
|
}
|
|
503
|
+
routeEvent(serverPubkey, mcp);
|
|
504
|
+
}
|
|
505
|
+
function routeEvent(serverPubkey, mcp) {
|
|
125
506
|
if (mcp.method !== void 0 && sessions.has(serverPubkey)) {
|
|
126
507
|
const server = { pubkey: serverPubkey };
|
|
127
508
|
for (const handler of eventHandlers) handler(server, mcp);
|
|
128
509
|
}
|
|
129
510
|
}
|
|
130
511
|
async function publishMcp(server, relays, message) {
|
|
512
|
+
const capabilityTags = [
|
|
513
|
+
...encrypt2 ? [[SUPPORT_ENCRYPTION]] : [],
|
|
514
|
+
...encrypt2 && wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? [[SUPPORT_ENCRYPTION_EPHEMERAL]] : [],
|
|
515
|
+
[SUPPORT_OVERSIZED_TRANSFER],
|
|
516
|
+
[SUPPORT_OPEN_STREAM]
|
|
517
|
+
];
|
|
131
518
|
const inner = finalizeEvent(
|
|
132
|
-
{
|
|
519
|
+
{
|
|
520
|
+
kind: KIND_CVM,
|
|
521
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
522
|
+
tags: [["p", server.pubkey], ...capabilityTags],
|
|
523
|
+
content: JSON.stringify(message)
|
|
524
|
+
},
|
|
133
525
|
clientSecretKey
|
|
134
526
|
);
|
|
135
527
|
if (!encrypt2) {
|
|
@@ -153,17 +545,52 @@ function createNostrCvmTransport(options = {}) {
|
|
|
153
545
|
function sendCorrelated(server, relays, message, timeout) {
|
|
154
546
|
const correlationId = nextCorrelationId();
|
|
155
547
|
const originalId = message.id;
|
|
156
|
-
|
|
548
|
+
let outgoing = { ...message, id: correlationId };
|
|
549
|
+
let issuedToken;
|
|
550
|
+
const params = message.params && typeof message.params === "object" ? message.params : void 0;
|
|
551
|
+
const meta = params?._meta && typeof params._meta === "object" ? params._meta : void 0;
|
|
552
|
+
const explicit = meta?.progressToken;
|
|
553
|
+
if (message.method === "tools/call") {
|
|
554
|
+
issuedToken = typeof explicit === "string" || typeof explicit === "number" ? explicit : correlationId;
|
|
555
|
+
outgoing = {
|
|
556
|
+
...outgoing,
|
|
557
|
+
params: {
|
|
558
|
+
...params,
|
|
559
|
+
_meta: {
|
|
560
|
+
...meta,
|
|
561
|
+
progressToken: issuedToken
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
} else if (typeof explicit === "string" || typeof explicit === "number") {
|
|
566
|
+
issuedToken = explicit;
|
|
567
|
+
}
|
|
568
|
+
const tokenKey = progressTokenKey(issuedToken);
|
|
569
|
+
const issuedKey = tokenKey ? `${server.pubkey}:${tokenKey}` : null;
|
|
570
|
+
if (issuedKey && issuedTokens.has(issuedKey)) {
|
|
571
|
+
return Promise.reject(new Error("duplicate progress token"));
|
|
572
|
+
}
|
|
157
573
|
return new Promise((resolve, reject) => {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
reject
|
|
161
|
-
|
|
162
|
-
|
|
574
|
+
const entry = {
|
|
575
|
+
resolve,
|
|
576
|
+
reject,
|
|
577
|
+
timer: setTimeout(() => {
|
|
578
|
+
const settled = settlePending(correlationId);
|
|
579
|
+
settled?.reject(new Error("relay timeout"));
|
|
580
|
+
}, timeout),
|
|
581
|
+
timeoutMs: timeout,
|
|
582
|
+
originalId,
|
|
583
|
+
serverPubkey: server.pubkey,
|
|
584
|
+
correlationId
|
|
585
|
+
};
|
|
586
|
+
if (issuedKey) {
|
|
587
|
+
entry.tokenKey = issuedKey;
|
|
588
|
+
issuedTokens.set(issuedKey, correlationId);
|
|
589
|
+
}
|
|
590
|
+
pending.set(correlationId, entry);
|
|
163
591
|
void publishMcp(server, relays, outgoing).catch((err) => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
reject(err instanceof Error ? err : new Error("publish failed"));
|
|
592
|
+
const settled = settlePending(correlationId);
|
|
593
|
+
settled?.reject(err instanceof Error ? err : new Error("publish failed"));
|
|
167
594
|
});
|
|
168
595
|
});
|
|
169
596
|
}
|
|
@@ -261,6 +688,10 @@ function createNostrCvmTransport(options = {}) {
|
|
|
261
688
|
if (!session) return;
|
|
262
689
|
sessions.delete(server.pubkey);
|
|
263
690
|
releaseRelays(session.relays);
|
|
691
|
+
for (const [correlationId, entry] of pending) {
|
|
692
|
+
if (entry.serverPubkey !== server.pubkey) continue;
|
|
693
|
+
settlePending(correlationId)?.reject(new Error("server closed"));
|
|
694
|
+
}
|
|
264
695
|
},
|
|
265
696
|
onEvent(handler) {
|
|
266
697
|
eventHandlers.add(handler);
|
|
@@ -275,9 +706,12 @@ function createNostrCvmTransport(options = {}) {
|
|
|
275
706
|
inbound = null;
|
|
276
707
|
for (const entry of pending.values()) {
|
|
277
708
|
clearTimeout(entry.timer);
|
|
709
|
+
releaseTransfer(entry);
|
|
710
|
+
releaseStream(entry);
|
|
278
711
|
entry.reject(new Error("transport disposed"));
|
|
279
712
|
}
|
|
280
713
|
pending.clear();
|
|
714
|
+
issuedTokens.clear();
|
|
281
715
|
sessions.clear();
|
|
282
716
|
relayRefcount.clear();
|
|
283
717
|
eventHandlers.clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cvm-nostr-transport.ts"],"sourcesContent":["/**\n * cvm-nostr-transport.ts — concrete ContextVM transport for NAP-CVM.\n *\n * Implements {@link CvmTransport} over Nostr, exactly as validated against live\n * ContextVM servers (e.g. Relatr):\n *\n * - MCP JSON-RPC messages ride in kind-25910 event `content`.\n * - Requests are CEP-4 gift-wrapped: the inner kind-25910 event is signed with\n * the shell's ephemeral client key, NIP-44-encrypted to the server, and\n * placed in a kind-21059 (ephemeral) / 1059 (regular) wrap signed by a fresh\n * random key, `p`-tagged to the server. Responses arrive the same way,\n * `p`-tagged to the client, and are correlated by the inner JSON-RPC `id`.\n * - Discovery reads kind-11316 (server) + kind-11317 (tools) announcements.\n *\n * Shipped on a separate entry (`@kehto/services/cvm-nostr-transport`) so the\n * `nostr-tools` dependency stays out of the core `@kehto/services` bundle.\n *\n * The client key is ephemeral and shell-owned: napplets never see keys, relay\n * sockets, or NIP-44 material (NAP-CVM §Security).\n *\n * @example\n * ```ts\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n * const transport = createNostrCvmTransport({\n * defaultRelays: ['wss://relay.contextvm.org', 'wss://relay2.contextvm.org'],\n * });\n * ```\n *\n * @module\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { finalizeEvent, generateSecretKey, getPublicKey, verifyEvent } from 'nostr-tools/pure';\nimport * as nip44 from 'nostr-tools/nip44';\nimport type { Event as NostrToolsEvent, Filter as NostrToolsFilter } from 'nostr-tools';\n\nimport type { CvmTransport } from './cvm-service.js';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** ContextVM unified transport event kind. */\nconst KIND_CVM = 25910;\n/** CEP-4 gift-wrap kinds: ephemeral (CEP-19) and regular. */\nconst KIND_GIFT_WRAP_EPHEMERAL = 21059;\nconst KIND_GIFT_WRAP_REGULAR = 1059;\n/** CEP-6 announcement kinds. */\nconst KIND_ANNOUNCE_SERVER = 11316;\nconst KIND_ANNOUNCE_TOOLS = 11317;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_DISCOVER_TIMEOUT_MS = 6_000;\nconst MCP_PROTOCOL_VERSION = '2025-11-25';\nconst SEEN_WRAP_LIMIT = 512;\n\n/** Minimal signed Nostr event. */\nexport interface NostrEventLike {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** A Nostr REQ filter (subset). */\nexport interface NostrFilterLike {\n kinds?: number[];\n authors?: string[];\n limit?: number;\n ['#p']?: string[];\n [key: string]: unknown;\n}\n\n/** Subscription handle returned by the relay pool. */\nexport interface CvmSubCloser {\n close(): void;\n}\n\n/**\n * Minimal relay-pool surface used by this transport — structurally satisfied\n * by `nostr-tools` `SimplePool`. Injectable for testing.\n */\nexport interface CvmRelayPool {\n subscribe(\n relays: string[],\n filter: NostrFilterLike,\n params: { onevent?: (event: NostrEventLike) => void; oneose?: () => void },\n ): CvmSubCloser;\n publish(relays: string[], event: NostrEventLike): void | Promise<unknown>;\n}\n\n/** Options for {@link createNostrCvmTransport}. */\nexport interface NostrCvmTransportOptions {\n /** Relays used when a server reference carries no relay hints. */\n defaultRelays?: string[];\n /** Default per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Whether to CEP-4 gift-wrap requests. Default true (most servers require it). */\n encrypt?: boolean;\n /** Use ephemeral (kind 21059) gift wraps when encrypting. Default true. */\n ephemeralWrap?: boolean;\n /** Relay pool to use. Defaults to a fresh `nostr-tools` `SimplePool`. */\n pool?: CvmRelayPool;\n /** Client secret key (32 bytes). Defaults to a generated ephemeral key. */\n clientSecretKey?: Uint8Array;\n /** Client info advertised during MCP `initialize`. */\n clientInfo?: { name: string; version: string };\n}\n\ntype EventHandler = (server: CvmServerRef, message: McpMessage) => void;\n\ninterface PendingRequest {\n resolve(message: McpMessage): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n /** The caller's original JSON-RPC id, restored on the response. */\n originalId: string | number | undefined;\n /** Server identity that must sign the correlated inner response. */\n serverPubkey: string;\n}\n\ninterface ServerSession {\n relays: string[];\n initialized: boolean;\n initializing: Promise<void> | null;\n}\n\nlet correlationCounter = 0;\nfunction nextCorrelationId(): string {\n correlationCounter += 1;\n return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;\n}\n\nfunction randomizedPastTimestamp(): number {\n // NIP-59: randomize within the past two days to reduce timing metadata.\n const jitter = Math.floor(Math.random() * 172_800);\n return Math.floor(Date.now() / 1000) - jitter;\n}\n\nfunction tagValue(tags: string[][], name: string): string | undefined {\n return tags.find((tag) => tag[0] === name)?.[1];\n}\n\n/** Adapt a `nostr-tools` SimplePool to the {@link CvmRelayPool} surface. */\nfunction simplePoolAdapter(sp: SimplePool): CvmRelayPool {\n return {\n subscribe(relays, filter, params) {\n return sp.subscribe(relays, filter as NostrToolsFilter, {\n onevent: params.onevent,\n oneose: params.oneose,\n });\n },\n async publish(relays, event) {\n const attempts = sp.publish(relays, event as NostrToolsEvent);\n if (attempts.length === 0) throw new Error('server not found');\n await Promise.any(attempts);\n },\n };\n}\n\n/**\n * Create a Nostr-backed ContextVM transport.\n *\n * @param options - Relay set, timeouts, encryption mode, and optional injected\n * pool/keys (the injected pool + key make the transport deterministic in tests).\n * @returns A {@link CvmTransport} plus a `dispose()` to tear down subscriptions.\n */\nexport function createNostrCvmTransport(\n options: NostrCvmTransportOptions = {},\n): CvmTransport & { dispose(): void } {\n const defaultRelays = options.defaultRelays ?? [];\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const encrypt = options.encrypt ?? true;\n const wrapKind = options.ephemeralWrap === false ? KIND_GIFT_WRAP_REGULAR : KIND_GIFT_WRAP_EPHEMERAL;\n const pool: CvmRelayPool = options.pool ?? simplePoolAdapter(new SimplePool());\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const clientInfo = options.clientInfo ?? { name: 'kehto-cvm', version: '1.0.0' };\n\n const sessions = new Map<string, ServerSession>();\n const pending = new Map<string, PendingRequest>();\n const eventHandlers = new Set<EventHandler>();\n const relayRefcount = new Map<string, number>();\n const seenWraps = new Set<string>();\n let inbound: CvmSubCloser | null = null;\n let subscribedRelays = '';\n\n function resolveRelays(server: CvmServerRef): string[] {\n const relays = server.relays && server.relays.length > 0 ? server.relays : defaultRelays;\n if (relays.length === 0) throw new Error('server not found');\n return [...new Set(relays)];\n }\n\n function holdRelays(relays: string[]): void {\n for (const url of relays) relayRefcount.set(url, (relayRefcount.get(url) ?? 0) + 1);\n refreshSubscription();\n }\n\n function releaseRelays(relays: string[]): void {\n for (const url of relays) {\n const count = (relayRefcount.get(url) ?? 0) - 1;\n if (count <= 0) relayRefcount.delete(url);\n else relayRefcount.set(url, count);\n }\n refreshSubscription();\n }\n\n function refreshSubscription(): void {\n const relays = [...relayRefcount.keys()].sort();\n const key = relays.join(',');\n if (key === subscribedRelays) return;\n inbound?.close();\n subscribedRelays = key;\n inbound = relays.length === 0\n ? null\n : pool.subscribe(\n relays,\n { kinds: encrypt ? [KIND_GIFT_WRAP_REGULAR, KIND_GIFT_WRAP_EPHEMERAL] : [KIND_CVM], ['#p']: [clientPubkey] },\n { onevent: handleInbound },\n );\n }\n\n function rememberWrap(id: string): boolean {\n if (seenWraps.has(id)) return false;\n seenWraps.add(id);\n if (seenWraps.size > SEEN_WRAP_LIMIT) {\n const oldest = seenWraps.values().next().value;\n if (oldest !== undefined) seenWraps.delete(oldest);\n }\n return true;\n }\n\n function handleInbound(event: NostrEventLike): void {\n if (!verifyEvent(event as NostrToolsEvent)) return;\n if (!event.tags.some((tag) => tag[0] === 'p' && tag[1] === clientPubkey)) return;\n if (!rememberWrap(event.id)) return;\n let serverPubkey: string;\n let mcp: McpMessage;\n try {\n if (encrypt) {\n const conversationKey = nip44.getConversationKey(clientSecretKey, event.pubkey);\n const inner = JSON.parse(nip44.decrypt(event.content, conversationKey)) as NostrEventLike;\n if (!verifyEvent(inner as NostrToolsEvent)) return;\n if (!inner.tags.some((tag) => tag[0] === 'p' && tag[1] === clientPubkey)) return;\n serverPubkey = inner.pubkey;\n mcp = JSON.parse(inner.content) as McpMessage;\n } else {\n serverPubkey = event.pubkey;\n mcp = JSON.parse(event.content) as McpMessage;\n }\n } catch {\n return; // not addressed to us / undecryptable / malformed — ignore.\n }\n\n const id = mcp.id;\n if (id != null && pending.has(String(id))) {\n const entry = pending.get(String(id))!;\n if (entry.serverPubkey !== serverPubkey) return;\n pending.delete(String(id));\n clearTimeout(entry.timer);\n entry.resolve({ ...mcp, id: entry.originalId });\n return;\n }\n // Uncorrelated server message (notification) → fan out as a CVM event.\n if (mcp.method !== undefined && sessions.has(serverPubkey)) {\n const server: CvmServerRef = { pubkey: serverPubkey };\n for (const handler of eventHandlers) handler(server, mcp);\n }\n }\n\n async function publishMcp(server: CvmServerRef, relays: string[], message: McpMessage): Promise<void> {\n const inner = finalizeEvent(\n { kind: KIND_CVM, created_at: Math.floor(Date.now() / 1000), tags: [['p', server.pubkey]], content: JSON.stringify(message) },\n clientSecretKey,\n ) as NostrEventLike;\n if (!encrypt) {\n await pool.publish(relays, inner);\n return;\n }\n const wrapSecretKey = generateSecretKey();\n const conversationKey = nip44.getConversationKey(wrapSecretKey, server.pubkey);\n // Ephemeral wraps (kind 21059) are not stored; relays reject backdated\n // ephemeral events as \"expired\", so they MUST carry a current timestamp.\n // Regular wraps (kind 1059) are backdated per NIP-59 to blur timing metadata.\n const createdAt =\n wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? Math.floor(Date.now() / 1000) : randomizedPastTimestamp();\n const wrap = finalizeEvent(\n {\n kind: wrapKind,\n created_at: createdAt,\n tags: [['p', server.pubkey]],\n content: nip44.encrypt(JSON.stringify(inner), conversationKey),\n },\n wrapSecretKey,\n ) as NostrEventLike;\n await pool.publish(relays, wrap);\n }\n\n function sendCorrelated(\n server: CvmServerRef,\n relays: string[],\n message: McpMessage,\n timeout: number,\n ): Promise<McpMessage> {\n const correlationId = nextCorrelationId();\n const originalId = message.id;\n const outgoing: McpMessage = { ...message, id: correlationId };\n return new Promise<McpMessage>((resolve, reject) => {\n const timer = setTimeout(() => {\n pending.delete(correlationId);\n reject(new Error('relay timeout'));\n }, timeout);\n pending.set(correlationId, { resolve, reject, timer, originalId, serverPubkey: server.pubkey });\n void publishMcp(server, relays, outgoing).catch((err: unknown) => {\n pending.delete(correlationId);\n clearTimeout(timer);\n reject(err instanceof Error ? err : new Error('publish failed'));\n });\n });\n }\n\n function getSession(server: CvmServerRef): ServerSession {\n let session = sessions.get(server.pubkey);\n if (!session) {\n const relays = resolveRelays(server);\n session = { relays, initialized: false, initializing: null };\n sessions.set(server.pubkey, session);\n holdRelays(relays);\n }\n return session;\n }\n\n async function ensureInitialized(server: CvmServerRef, session: ServerSession, timeout: number): Promise<void> {\n if (session.initialized) return;\n if (session.initializing) return session.initializing;\n session.initializing = (async () => {\n try {\n await sendCorrelated(\n server,\n session.relays,\n {\n jsonrpc: '2.0',\n id: 'init',\n method: 'initialize',\n params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo },\n },\n timeout,\n );\n // notifications/initialized completes the handshake; no response expected.\n await publishMcp(server, session.relays, { jsonrpc: '2.0', method: 'notifications/initialized' });\n session.initialized = true;\n } catch {\n throw new Error('initialization failed');\n } finally {\n session.initializing = null;\n }\n })();\n return session.initializing;\n }\n\n return {\n async discover(query?: CvmDiscoverQuery): Promise<CvmServer[]> {\n const relays = query?.relays && query.relays.length > 0 ? query.relays : defaultRelays;\n if (relays.length === 0) return [];\n const announces = new Map<string, NostrEventLike>();\n const toolLists = new Map<string, NostrEventLike>();\n await new Promise<void>((resolve) => {\n const sub = pool.subscribe(\n relays,\n { kinds: [KIND_ANNOUNCE_SERVER, KIND_ANNOUNCE_TOOLS], limit: query?.limit ? query.limit * 4 : 100 },\n {\n onevent(event) {\n if (event.kind === KIND_ANNOUNCE_SERVER) announces.set(event.pubkey, event);\n else if (event.kind === KIND_ANNOUNCE_TOOLS) toolLists.set(event.pubkey, event);\n },\n oneose() { resolve(); },\n },\n );\n setTimeout(() => { sub.close(); resolve(); }, DEFAULT_DISCOVER_TIMEOUT_MS);\n });\n\n const servers: CvmServer[] = [];\n for (const [pubkey, event] of announces) {\n const name = tagValue(event.tags, 'name');\n const description = tagValue(event.tags, 'about');\n const server: CvmServer = {\n pubkey,\n relays: [...relays],\n ...(name ? { name } : {}),\n ...(description ? { description } : {}),\n paymentRequired: false,\n };\n const tools = toolLists.get(pubkey);\n if (tools) {\n const names = tools.tags.filter((tag) => tag[0] === 'i' && typeof tag[2] === 'string').map((tag) => tag[2]);\n if (names.length > 0) server.capabilities = names;\n }\n servers.push(server);\n }\n\n const search = query?.search?.toLowerCase();\n const filtered = search\n ? servers.filter((s) => `${s.name ?? ''} ${s.description ?? ''}`.toLowerCase().includes(search))\n : servers;\n return query?.limit ? filtered.slice(0, query.limit) : filtered;\n },\n\n async request(server: CvmServerRef, message: McpMessage, requestOptions?: CvmRequestOptions): Promise<McpMessage> {\n const session = getSession(server);\n const timeout = requestOptions?.timeoutMs ?? timeoutMs;\n if (requestOptions?.initialize) await ensureInitialized(server, session, timeout);\n return sendCorrelated(server, session.relays, message, timeout);\n },\n\n async close(server: CvmServerRef): Promise<void> {\n const session = sessions.get(server.pubkey);\n if (!session) return;\n sessions.delete(server.pubkey);\n releaseRelays(session.relays);\n },\n\n onEvent(handler: EventHandler): { close(): void } {\n eventHandlers.add(handler);\n return {\n close() {\n eventHandlers.delete(handler);\n },\n };\n },\n\n dispose(): void {\n inbound?.close();\n inbound = null;\n for (const entry of pending.values()) {\n clearTimeout(entry.timer);\n entry.reject(new Error('transport disposed'));\n }\n pending.clear();\n sessions.clear();\n relayRefcount.clear();\n eventHandlers.clear();\n subscribedRelays = '';\n },\n };\n}\n"],"mappings":"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","names":["encrypt"]}
|
|
1
|
+
{"version":3,"sources":["../src/cvm-nostr-transport.ts"],"sourcesContent":["/**\n * cvm-nostr-transport.ts — concrete ContextVM transport for NAP-CVM.\n *\n * Implements {@link CvmTransport} over Nostr, exactly as validated against live\n * ContextVM servers (e.g. Relatr):\n *\n * - MCP JSON-RPC messages ride in kind-25910 event `content`.\n * - Requests are CEP-4 gift-wrapped: the inner kind-25910 event is signed with\n * the shell's ephemeral client key, NIP-44-encrypted to the server, and\n * placed in a kind-21059 (ephemeral) / 1059 (regular) wrap signed by a fresh\n * random key, `p`-tagged to the server. Responses arrive the same way,\n * `p`-tagged to the client, and are correlated by the inner JSON-RPC `id`.\n * - Discovery reads kind-11316 (server) + kind-11317 (tools) announcements.\n * - CEP-22 oversized transfers and CEP-41 open streams ride\n * `notifications/progress` frames addressed by the request progressToken.\n *\n * Shipped on a separate entry (`@kehto/services/cvm-nostr-transport`) so the\n * `nostr-tools` dependency stays out of the core `@kehto/services` bundle.\n *\n * The client key is ephemeral and shell-owned: napplets never see keys, relay\n * sockets, or NIP-44 material (NAP-CVM §Security).\n *\n * @example\n * ```ts\n * import { createNostrCvmTransport } from '@kehto/services/cvm-nostr-transport';\n * const transport = createNostrCvmTransport({\n * defaultRelays: ['wss://relay.contextvm.org', 'wss://relay2.contextvm.org'],\n * });\n * ```\n *\n * @module\n */\n\nimport { SimplePool } from 'nostr-tools/pool';\nimport { finalizeEvent, generateSecretKey, getPublicKey, verifyEvent } from 'nostr-tools/pure';\nimport * as nip44 from 'nostr-tools/nip44';\nimport type { Event as NostrToolsEvent, Filter as NostrToolsFilter } from 'nostr-tools';\n\nimport type { CvmTransport } from './cvm-service.js';\nimport type {\n CvmDiscoverQuery,\n CvmRequestOptions,\n CvmServer,\n CvmServerRef,\n McpMessage,\n} from './cvm-types.js';\n\n/** ContextVM unified transport event kind. */\nconst KIND_CVM = 25910;\n/** CEP-4 gift-wrap kinds: ephemeral (CEP-19) and regular. */\nconst KIND_GIFT_WRAP_EPHEMERAL = 21059;\nconst KIND_GIFT_WRAP_REGULAR = 1059;\n/** CEP-6 announcement kinds. */\nconst KIND_ANNOUNCE_SERVER = 11316;\nconst KIND_ANNOUNCE_TOOLS = 11317;\n\n/** ContextVM capability discovery tags (single-element tags per CEP). */\nconst SUPPORT_ENCRYPTION = 'support_encryption';\nconst SUPPORT_ENCRYPTION_EPHEMERAL = 'support_encryption_ephemeral';\nconst SUPPORT_OVERSIZED_TRANSFER = 'support_oversized_transfer';\nconst SUPPORT_OPEN_STREAM = 'support_open_stream';\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_DISCOVER_TIMEOUT_MS = 6_000;\nconst MCP_PROTOCOL_VERSION = '2025-11-25';\nconst SEEN_WRAP_LIMIT = 512;\n/** CEP-22 oversized-transfer: digest is `sha256:<hex>` prefixed. */\nconst DIGEST_PREFIX = 'sha256:';\n/** Safety caps for CEP-22 reassembly. */\nconst MAX_TRANSFER_CHUNKS = 10_000;\nconst MAX_TRANSFER_BYTES = 100 * 1024 * 1024;\n/** Concurrent CEP-22 reassemblies admitted across all servers. */\nconst MAX_ACTIVE_TRANSFERS = 8;\n/** Hard ceiling on how long one reassembly may hold buffered chunks. */\nconst MAX_TRANSFER_LIFETIME_MS = 120_000;\n/** The only CEP-22 completion mode; receivers must reject all others. */\nconst COMPLETION_MODE_RENDER = 'render';\n/** CEP-41: default idle interval before a keepalive ping is sent. */\nconst DEFAULT_STREAM_IDLE_TIMEOUT_MS = 30_000;\n/** CEP-41: default probe deadline waiting for a pong after our ping. */\nconst DEFAULT_STREAM_PROBE_TIMEOUT_MS = 10_000;\n/** CEP-41: bounded out-of-order chunk gap buffer (local resource policy). */\nconst MAX_STREAM_BUFFERED_CHUNKS = 256;\nconst MAX_STREAM_BUFFERED_CODE_UNITS = 8 * 1024 * 1024;\n/** CEP-41: local maximum ping nonce size receivers SHOULD enforce. */\nconst MAX_PING_NONCE_BYTES = 64;\n\n/** Minimal signed Nostr event. */\nexport interface NostrEventLike {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** A Nostr REQ filter (subset). */\nexport interface NostrFilterLike {\n kinds?: number[];\n authors?: string[];\n limit?: number;\n ['#p']?: string[];\n [key: string]: unknown;\n}\n\n/** Subscription handle returned by the relay pool. */\nexport interface CvmSubCloser {\n close(): void;\n}\n\n/**\n * Minimal relay-pool surface used by this transport — structurally satisfied\n * by `nostr-tools` `SimplePool`. Injectable for testing.\n */\nexport interface CvmRelayPool {\n subscribe(\n relays: string[],\n filter: NostrFilterLike,\n params: { onevent?: (event: NostrEventLike) => void; oneose?: () => void },\n ): CvmSubCloser;\n publish(relays: string[], event: NostrEventLike): void | Promise<unknown>;\n}\n\n/** Options for {@link createNostrCvmTransport}. */\nexport interface NostrCvmTransportOptions {\n /** Relays used when a server reference carries no relay hints. */\n defaultRelays?: string[];\n /** Default per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Whether to CEP-4 gift-wrap requests. Default true (most servers require it). */\n encrypt?: boolean;\n /** Use ephemeral (kind 21059) gift wraps when encrypting. Default true. */\n ephemeralWrap?: boolean;\n /** Relay pool to use. Defaults to a fresh `nostr-tools` `SimplePool`. */\n pool?: CvmRelayPool;\n /** Client secret key (32 bytes). Defaults to a generated ephemeral key. */\n clientSecretKey?: Uint8Array;\n /** Client info advertised during MCP `initialize`. */\n clientInfo?: { name: string; version: string };\n /** CEP-41: per-stream idle timeout before a keepalive ping is sent. */\n streamIdleTimeoutMs?: number;\n /** CEP-41: probe deadline waiting for a pong after a keepalive ping. */\n streamProbeTimeoutMs?: number;\n}\n\ntype EventHandler = (server: CvmServerRef, message: McpMessage) => void;\n\ninterface PendingRequest {\n resolve(message: McpMessage): void;\n reject(error: Error): void;\n timer: ReturnType<typeof setTimeout>;\n timeoutMs: number;\n /** The caller's original JSON-RPC id, restored on the response. */\n originalId: string | number | undefined;\n /** Server identity that must sign the correlated inner response. */\n serverPubkey: string;\n /** Wire correlation id this entry is keyed by. */\n correlationId: string;\n /** `<serverPubkey>:<typed token>` key of the issued progress token, if any. */\n tokenKey?: string;\n /** CEP-22 reassembly bound to this request; never shared across requests. */\n oversized?: OversizedTransfer;\n /** CEP-41 open-stream state; undefined once the stream terminates. */\n stream?: OpenStreamState;\n /**\n * Terminal marker: open-stream frames are ignored, but request admission\n * (the token binding and any CEP-22 reassembly) survives until the request\n * settles, so the final JSON-RPC response remains admissible.\n */\n streamEnded?: boolean;\n}\n\ninterface ServerSession {\n relays: string[];\n initialized: boolean;\n initializing: Promise<void> | null;\n}\n\nlet correlationCounter = 0;\nfunction nextCorrelationId(): string {\n correlationCounter += 1;\n return `cvm-${correlationCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;\n}\n\nlet streamNonceCounter = 0;\n/** Unique ping nonce within a stream, well under the 64-byte maximum. */\nfunction nextStreamNonce(): string {\n streamNonceCounter += 1;\n return `cvm-ping-${streamNonceCounter}-${getPublicKey(generateSecretKey()).slice(0, 8)}`;\n}\n\nfunction randomizedPastTimestamp(): number {\n // NIP-59: randomize within the past two days to reduce timing metadata.\n const jitter = Math.floor(Math.random() * 172_800);\n return Math.floor(Date.now() / 1000) - jitter;\n}\n\nfunction tagValue(tags: string[][], name: string): string | undefined {\n return tags.find((tag) => tag[0] === name)?.[1];\n}\n\n/**\n * Type-preserving key for a `string | number` MCP progress token, so numeric\n * and string tokens never collide in internal state.\n */\nfunction progressTokenKey(token: string | number | undefined): string | null {\n if (typeof token === 'string') return token.length > 0 ? `s:${token}` : null;\n if (typeof token === 'number' && Number.isFinite(token)) return `n:${token}`;\n return null;\n}\n\n/** True when value is a finite integer within [min, max]. */\nfunction isBoundedInteger(value: unknown, min: number, max: number): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;\n}\n\n/* ------------------------- CEP-22 / CEP-41 framing ------------------------ */\n\n/**\n * A CEP-22 oversized-transfer / CEP-41 open-stream frame. Both ride\n * `notifications/progress` with `params.progressToken` set to the originating\n * request's token and `params.cvm` describing the frame. Demux on `cvm.type`.\n */\ninterface CvmProgressFrame {\n type?: string;\n frameType?: string;\n /** CEP-22: required completion mode (\"render\"). */\n completionMode?: string;\n /** CEP-22: reassembly metadata. */\n digest?: string;\n totalBytes?: number;\n totalChunks?: number;\n /** CEP-22 chunk / CEP-41 chunk payload (JSON-serialized). */\n data?: string;\n /** CEP-41: streaming metadata. */\n chunkIndex?: number;\n lastChunkIndex?: number;\n nonce?: string;\n reason?: string;\n}\n\ninterface CvmProgressParams {\n progressToken?: string | number;\n progress?: number;\n cvm?: CvmProgressFrame;\n}\n\n/** In-flight CEP-22 oversized transfer being reassembled. */\ninterface OversizedTransfer {\n /** sha256 of the exact serialized payload; required, always verified. */\n digest: string;\n /** Declared exact byte length; required, always verified. */\n totalBytes: number;\n /** Declared chunk count; required, never inferred from arrivals. */\n totalChunks: number;\n startProgress: number;\n acceptProgress: number | null;\n /** Chunk slices keyed by the canonical outer `params.progress` value. */\n chunks: Map<number, string>;\n /**\n * Buffered UTF-16 code units across stored chunks — an independent memory\n * cap, not a byte check: a surrogate pair split across a chunk boundary\n * encodes each half as U+FFFD (3 bytes) in isolation, so per-chunk UTF-8\n * accounting rejects payloads whose joined length and digest are correct.\n * The exact UTF-8 byte length is verified on the joined payload at end.\n */\n codeUnitsReceived: number;\n /** Hard expiry; progress frames never extend it. */\n expiryTimer: ReturnType<typeof setTimeout>;\n}\n\n/** In-flight CEP-41 open stream bound to a pending request. */\ninterface OpenStreamState {\n /** Typed stream token (the request's progressToken) for frames we send. */\n token: string | number;\n /** Last admitted frame progress; every frame must increase it (CEP-41). */\n lastProgress: number;\n /** Our own outgoing frame progress (ping/pong/abort), monotonic per stream. */\n localProgress: number;\n /** Next contiguous chunkIndex expected; chunks fan out strictly in order. */\n nextChunkIndex: number;\n /** Bounded relay-reordered chunk gap buffer keyed by chunkIndex (CEP-41 MAY). */\n outOfOrder: Map<number, { message: McpMessage; progress: number; codeUnits: number }>;\n /** UTF-16 code units held in the gap buffer (local memory policy). */\n outOfOrderCodeUnits: number;\n /** Nonce of our outstanding keepalive ping; a pong must match it. */\n pendingNonce: string | null;\n /** Idle watchdog: fires a keepalive ping when no valid frame arrives. */\n idleTimer: ReturnType<typeof setTimeout>;\n /** Probe deadline for the outstanding ping's pong. */\n probeTimer: ReturnType<typeof setTimeout> | null;\n}\n\nasync function sha256Hex(text: string): Promise<string> {\n const bytes = new TextEncoder().encode(text);\n const buf = await crypto.subtle.digest('SHA-256', bytes);\n return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** Adapt a `nostr-tools` SimplePool to the {@link CvmRelayPool} surface. */\nfunction simplePoolAdapter(sp: SimplePool): CvmRelayPool {\n return {\n subscribe(relays, filter, params) {\n return sp.subscribe(relays, filter as NostrToolsFilter, {\n onevent: params.onevent,\n oneose: params.oneose,\n });\n },\n async publish(relays, event) {\n const attempts = sp.publish(relays, event as NostrToolsEvent);\n if (attempts.length === 0) throw new Error('server not found');\n await Promise.any(attempts);\n },\n };\n}\n\n/**\n * Create a Nostr-backed ContextVM transport.\n *\n * @param options - Relay set, timeouts, encryption mode, and optional injected\n * pool/keys (the injected pool + key make the transport deterministic in tests).\n * @returns A {@link CvmTransport} plus a `dispose()` to tear down subscriptions.\n */\nexport function createNostrCvmTransport(\n options: NostrCvmTransportOptions = {},\n): CvmTransport & { dispose(): void } {\n const defaultRelays = options.defaultRelays ?? [];\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const encrypt = options.encrypt ?? true;\n const wrapKind = options.ephemeralWrap === false ? KIND_GIFT_WRAP_REGULAR : KIND_GIFT_WRAP_EPHEMERAL;\n const pool: CvmRelayPool = options.pool ?? simplePoolAdapter(new SimplePool());\n const clientSecretKey = options.clientSecretKey ?? generateSecretKey();\n const clientPubkey = getPublicKey(clientSecretKey);\n const clientInfo = options.clientInfo ?? { name: 'kehto-cvm', version: '1.0.0' };\n const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;\n const streamProbeTimeoutMs = options.streamProbeTimeoutMs ?? DEFAULT_STREAM_PROBE_TIMEOUT_MS;\n\n const sessions = new Map<string, ServerSession>();\n const pending = new Map<string, PendingRequest>();\n const eventHandlers = new Set<EventHandler>();\n const relayRefcount = new Map<string, number>();\n const seenWraps = new Set<string>();\n /**\n * Issued progress tokens, `<serverPubkey>:<typed token>` → correlation id.\n * CEP-22/CEP-41 frames are admitted only for tokens this client issued to\n * the signing server, binding reassembly state to the expected server.\n */\n const issuedTokens = new Map<string, string>();\n let inbound: CvmSubCloser | null = null;\n let subscribedRelays = '';\n\n function resolveRelays(server: CvmServerRef): string[] {\n const relays = server.relays && server.relays.length > 0 ? server.relays : defaultRelays;\n if (relays.length === 0) throw new Error('server not found');\n return [...new Set(relays)];\n }\n\n function holdRelays(relays: string[]): void {\n for (const url of relays) relayRefcount.set(url, (relayRefcount.get(url) ?? 0) + 1);\n refreshSubscription();\n }\n\n function releaseRelays(relays: string[]): void {\n for (const url of relays) {\n const count = (relayRefcount.get(url) ?? 0) - 1;\n if (count <= 0) relayRefcount.delete(url);\n else relayRefcount.set(url, count);\n }\n refreshSubscription();\n }\n\n function refreshSubscription(): void {\n const relays = [...relayRefcount.keys()].sort();\n const key = relays.join(',');\n if (key === subscribedRelays) return;\n inbound?.close();\n subscribedRelays = key;\n inbound = relays.length === 0\n ? null\n : pool.subscribe(\n relays,\n { kinds: encrypt ? [KIND_GIFT_WRAP_REGULAR, KIND_GIFT_WRAP_EPHEMERAL] : [KIND_CVM], ['#p']: [clientPubkey] },\n { onevent: handleInbound },\n );\n }\n\n function rememberWrap(id: string): boolean {\n if (seenWraps.has(id)) return false;\n seenWraps.add(id);\n if (seenWraps.size > SEEN_WRAP_LIMIT) {\n const oldest = seenWraps.values().next().value;\n if (oldest !== undefined) seenWraps.delete(oldest);\n }\n return true;\n }\n\n function handleInbound(event: NostrEventLike): void {\n if (!verifyEvent(event as NostrToolsEvent)) return;\n if (!event.tags.some((tag) => tag[0] === 'p' && tag[1] === clientPubkey)) return;\n if (!rememberWrap(event.id)) return;\n let serverPubkey: string;\n let mcp: McpMessage;\n try {\n if (encrypt) {\n const conversationKey = nip44.getConversationKey(clientSecretKey, event.pubkey);\n const inner = JSON.parse(nip44.decrypt(event.content, conversationKey)) as NostrEventLike;\n if (!verifyEvent(inner as NostrToolsEvent)) return;\n if (!inner.tags.some((tag) => tag[0] === 'p' && tag[1] === clientPubkey)) return;\n serverPubkey = inner.pubkey;\n mcp = JSON.parse(inner.content) as McpMessage;\n } else {\n serverPubkey = event.pubkey;\n mcp = JSON.parse(event.content) as McpMessage;\n }\n } catch {\n return; // not addressed to us / undecryptable / malformed — ignore.\n }\n\n // CEP-22 oversized-transfer / CEP-41 open-stream frames ride\n // notifications/progress. Demux on cvm.type; neither resolves a pending\n // request directly (CEP-22 reassembles into a fresh JSON-RPC message that\n // re-enters {@link routeMessage}; CEP-41 streams + a separate normal\n // response resolves the request). Frames are admitted only for progress\n // tokens this client issued to the signing server — anything else is\n // dropped before it can touch state, timers, or event handlers.\n if (mcp.method === 'notifications/progress') {\n const params = (mcp.params ?? {}) as CvmProgressParams;\n const cvm = params.cvm;\n if (cvm && typeof cvm.type === 'string') {\n if (cvm.type === 'oversized-transfer') {\n void handleOversizedFrame(serverPubkey, params, cvm);\n return;\n }\n if (cvm.type === 'open-stream') {\n const entry = admitFrame(serverPubkey, params.progressToken);\n if (!entry) return;\n handleOpenStreamFrame(serverPubkey, entry, params, cvm, mcp);\n return;\n }\n }\n }\n\n // Correlated responses settle their pending request; uncorrelated server\n // messages fan out as CVM events.\n routeMessage(serverPubkey, mcp);\n }\n\n /**\n * Publish one local CEP-41 control frame (ping/pong/abort) on a stream.\n * The wire progressToken preserves the peer's `string | number` type: a\n * peer matching on its original token discards a stringified frame.\n */\n async function publishStreamFrame(\n entry: PendingRequest,\n stream: OpenStreamState,\n frame: { frameType: 'ping' | 'pong' | 'abort'; nonce?: string; reason?: string },\n ): Promise<void> {\n const session = sessions.get(entry.serverPubkey);\n if (!session) return;\n // CEP-41 progress is monotonic per direction: this counter numbers only\n // our outbound control frames. The peer's frames are tracked by the\n // inbound watermark (stream.lastProgress), which local frames must never\n // touch — the reference SDK matches pongs by nonce, not sequence position.\n stream.localProgress += 1;\n await publishMcp(\n { pubkey: entry.serverPubkey },\n session.relays,\n {\n jsonrpc: '2.0',\n method: 'notifications/progress',\n params: {\n progressToken: stream.token,\n progress: stream.localProgress,\n cvm: { type: 'open-stream', ...frame },\n },\n },\n );\n }\n\n /**\n * Admit a CEP-22/CEP-41 progress frame: the token must be one this client\n * issued to the signing server, and the request it belongs to must still\n * be in flight. Frames from any other signer are dropped before they can\n * touch state or timers (NAP-CVM: responses must be validated against the\n * expected server pubkey — a relay delivering a frame is not proof of\n * origin).\n */\n function admitFrame(serverPubkey: string, token: string | number | undefined): PendingRequest | null {\n const tokenKey = progressTokenKey(token);\n if (!tokenKey) return null;\n const correlationId = issuedTokens.get(`${serverPubkey}:${tokenKey}`);\n if (!correlationId) return null;\n const entry = pending.get(correlationId);\n if (!entry || entry.serverPubkey !== serverPubkey) return null;\n return entry;\n }\n\n /** Drop a request's CEP-22 reassembly buffers; the token binding survives. */\n function releaseTransfer(entry: PendingRequest): void {\n if (!entry.oversized) return;\n clearTimeout(entry.oversized.expiryTimer);\n entry.oversized = undefined;\n }\n\n /** Clear a stream's timers and gap buffer without touching admission. */\n function releaseStream(entry: PendingRequest): void {\n if (!entry.stream) return;\n clearTimeout(entry.stream.idleTimer);\n if (entry.stream.probeTimer) clearTimeout(entry.stream.probeTimer);\n entry.stream = undefined;\n }\n\n /**\n * Terminate a stream while preserving request admission: the token binding\n * and any CEP-22 reassembly survive until the request settles, so the\n * final JSON-RPC response (possibly oversized) remains admissible. CEP-41\n * close ends the stream, not the originating JSON-RPC request.\n */\n function endStream(entry: PendingRequest): void {\n releaseStream(entry);\n entry.streamEnded = true;\n }\n\n /**\n * Fail a stream (CEP-41): terminate it without treating it as successfully\n * completed, and send a best-effort abort with an advisory reason. The\n * originating request is NOT settled — its own deadline is the backstop.\n */\n function failStream(entry: PendingRequest, reason: string): void {\n const stream = entry.stream;\n if (!stream || entry.streamEnded) return;\n endStream(entry);\n void publishStreamFrame(entry, stream, { frameType: 'abort', reason }).catch(() => {\n // Best-effort: an abort we cannot publish changes nothing locally.\n });\n }\n\n /** Restart a stream's idle watchdog after valid frame activity. */\n function resetStreamIdleTimer(entry: PendingRequest, stream: OpenStreamState): void {\n clearTimeout(stream.idleTimer);\n stream.idleTimer = setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs);\n }\n\n /**\n * Idle watchdog: no valid frame arrived within the idle timeout, so probe\n * the peer with a keepalive ping (CEP-41 MUST). If no matching pong\n * arrives before the probe deadline, the stream fails.\n */\n function onStreamIdle(entry: PendingRequest): void {\n const stream = entry.stream;\n if (!stream || entry.streamEnded) return;\n if (stream.pendingNonce !== null) return; // a probe is already outstanding\n const session = sessions.get(entry.serverPubkey);\n if (!session) {\n failStream(entry, 'session closed');\n return;\n }\n stream.pendingNonce = nextStreamNonce();\n stream.probeTimer = setTimeout(() => failStream(entry, 'probe timeout'), streamProbeTimeoutMs);\n void publishStreamFrame(entry, stream, { frameType: 'ping', nonce: stream.pendingNonce })\n .catch(() => failStream(entry, 'ping publication failed'));\n }\n\n /** Drop all frame state a request owns: buffers, stream, and token binding. */\n function releaseFrameState(entry: PendingRequest): void {\n releaseTransfer(entry);\n releaseStream(entry);\n if (entry.tokenKey && issuedTokens.get(entry.tokenKey) === entry.correlationId) {\n issuedTokens.delete(entry.tokenKey);\n }\n entry.tokenKey = undefined;\n }\n\n /** Settle a pending request and release every timer and binding it owns. */\n function settlePending(correlationId: string): PendingRequest | null {\n const entry = pending.get(correlationId);\n if (!entry) return null;\n clearTimeout(entry.timer);\n pending.delete(correlationId);\n releaseFrameState(entry);\n return entry;\n }\n\n /** Number of in-flight CEP-22 reassemblies across all servers. */\n function countActiveTransfers(): number {\n let active = 0;\n for (const entry of pending.values()) {\n if (entry.oversized) active += 1;\n }\n return active;\n }\n\n /**\n * Reassemble a CEP-22 oversized-transfer. Frames are admitted only for the\n * server the token was issued to; chunk.data slices are joined in progress\n * order, verified against the start frame's sha256 digest, then parsed as\n * the real JSON-RPC message and re-entered into {@link routeMessage}\n * (which resolves the correlated pending request).\n */\n async function handleOversizedFrame(\n serverPubkey: string,\n params: CvmProgressParams,\n cvm: CvmProgressFrame,\n ): Promise<void> {\n const entry = admitFrame(serverPubkey, params.progressToken);\n if (!entry) return;\n const progress = Number(params.progress ?? NaN);\n // Every frame's progress must be a safe integer: past 2**53 the double\n // `p + 1 === p`, so range arithmetic on such values can never terminate.\n if (!Number.isSafeInteger(progress) || progress < 0) return;\n\n if (cvm.frameType === 'start') {\n // CEP-22 requires the full start shape before anything is buffered:\n // the render completion mode, the sha256 digest, and both totals within\n // local caps. Unsupported or incomplete starts fail without state.\n const digest = typeof cvm.digest === 'string' ? cvm.digest : '';\n const { totalBytes, totalChunks } = cvm;\n if (cvm.completionMode !== COMPLETION_MODE_RENDER) return;\n if (digest === '') return;\n if (!isBoundedInteger(totalChunks, 1, MAX_TRANSFER_CHUNKS)) return;\n if (!isBoundedInteger(totalBytes, 1, MAX_TRANSFER_BYTES)) return;\n // The whole transfer range (chunks + end) must stay within safe\n // integers, or reassembly arithmetic on it can never terminate.\n if (progress > Number.MAX_SAFE_INTEGER - totalChunks - 1) return;\n if (!entry.oversized && countActiveTransfers() >= MAX_ACTIVE_TRANSFERS) return;\n // A repeated start fails the previous transfer for the same token.\n releaseTransfer(entry);\n entry.oversized = {\n digest,\n totalBytes,\n totalChunks,\n startProgress: progress,\n acceptProgress: null,\n chunks: new Map(),\n codeUnitsReceived: 0,\n // Hard expiry: progress frames extend the request deadline but never\n // how long buffers may be held.\n expiryTimer: setTimeout(() => releaseTransfer(entry), MAX_TRANSFER_LIFETIME_MS),\n };\n refreshPendingTimeout(entry);\n return;\n }\n\n const transfer = entry.oversized;\n if (!transfer) return;\n\n if (cvm.frameType === 'accept') {\n // The optional accept immediately follows start in the canonical\n // transfer sequence. Any other value makes later ordering ambiguous.\n if (progress !== transfer.startProgress + 1) {\n releaseTransfer(entry);\n return;\n }\n transfer.acceptProgress = progress;\n refreshPendingTimeout(entry);\n return;\n }\n\n if (cvm.frameType === 'chunk') {\n if (typeof cvm.data !== 'string' || progress <= transfer.startProgress) return;\n // Duplicate progress values are malformed (CEP-22 progress strictly\n // increases); drop instead of double-counting buffered content.\n if (transfer.chunks.has(progress)) return;\n // The declared chunk count is exact (CEP-22); a sender exceeding it\n // fails the whole transfer.\n if (transfer.chunks.size >= transfer.totalChunks) {\n releaseTransfer(entry);\n return;\n }\n // Independent memory cap in UTF-16 code units. The declared totalBytes\n // is deliberately NOT compared during streaming: a surrogate pair split\n // across a chunk boundary makes per-chunk UTF-8 accounting unsound.\n // Exact byte length and digest are verified on the joined payload.\n transfer.codeUnitsReceived += cvm.data.length;\n if (transfer.codeUnitsReceived > MAX_TRANSFER_BYTES) {\n releaseTransfer(entry);\n return;\n }\n transfer.chunks.set(progress, cvm.data);\n refreshPendingTimeout(entry);\n return;\n }\n\n if (cvm.frameType === 'abort') {\n releaseTransfer(entry);\n return;\n }\n\n if (cvm.frameType === 'end') {\n const message = await assembleTransfer(transfer, progress);\n releaseTransfer(entry);\n if (message) routeMessage(serverPubkey, message);\n return;\n }\n }\n\n /** Validate a completed transfer and assemble its payload; null fails it. */\n async function assembleTransfer(transfer: OversizedTransfer, endProgress: number): Promise<McpMessage | null> {\n const { startProgress, acceptProgress, totalChunks } = transfer;\n // Iterate with a bounded chunk counter, never `progress++`: start\n // validation keeps `first + totalChunks` within safe integers, and the\n // counter terminates even if state were somehow corrupted.\n const hasCompleteRange = (first: number): boolean => {\n for (let i = 0; i < totalChunks; i++) {\n if (!transfer.chunks.has(first + i)) return false;\n }\n return true;\n };\n // Chunks follow start, one progress later when the sender waited for\n // our accept before transmitting.\n const directStart = startProgress + 1;\n const acceptGatedStart = startProgress + 2;\n const firstProgress = hasCompleteRange(directStart)\n && endProgress === directStart + totalChunks\n ? directStart\n : acceptProgress === startProgress + 1\n && hasCompleteRange(acceptGatedStart)\n && endProgress === acceptGatedStart + totalChunks\n ? acceptGatedStart\n : null;\n if (firstProgress === null) return null;\n let payload = '';\n for (let i = 0; i < totalChunks; i++) {\n payload += transfer.chunks.get(firstProgress + i)!;\n }\n // Both checks are mandatory (CEP-22): exact byte length and digest match\n // before the payload is materialized into a JSON-RPC message.\n if (new TextEncoder().encode(payload).byteLength !== transfer.totalBytes) return null;\n const expected = transfer.digest.startsWith(DIGEST_PREFIX)\n ? transfer.digest.slice(DIGEST_PREFIX.length)\n : transfer.digest;\n if ((await sha256Hex(payload)) !== expected) return null;\n try {\n return JSON.parse(payload) as McpMessage;\n } catch {\n return null;\n }\n }\n\n /**\n * Run the CEP-41 receiver state machine for one admitted open-stream\n * frame. A stream MUST begin with `start`; every frame MUST carry a\n * monotonically increasing safe-integer `progress`; chunks fan out in\n * contiguous `chunkIndex` order with a bounded out-of-order gap buffer;\n * `close`/`abort` are terminal. `start`/`accept`/`chunk`/`close`/`abort`\n * fan out through onEvent; `ping`/`pong` are transport keepalive and stay\n * internal. Terminal frames end the stream but never the originating\n * JSON-RPC request: the token binding survives until the request settles,\n * so an oversized (CEP-22) final response remains admissible.\n */\n function handleOpenStreamFrame(\n serverPubkey: string,\n entry: PendingRequest,\n params: CvmProgressParams,\n cvm: CvmProgressFrame,\n mcp: McpMessage,\n ): void {\n const progress = Number(params.progress ?? NaN);\n if (!Number.isSafeInteger(progress) || progress < 0) return;\n // Terminal streams ignore every later frame (CEP-41 post-close).\n if (entry.streamEnded) return;\n const stream = entry.stream;\n if (!stream) {\n // A stream MUST begin with start; anything else is dropped.\n if (cvm.frameType !== 'start') return;\n entry.stream = {\n token: params.progressToken as string | number,\n lastProgress: progress,\n localProgress: 0,\n nextChunkIndex: 0,\n outOfOrder: new Map(),\n outOfOrderCodeUnits: 0,\n pendingNonce: null,\n idleTimer: setTimeout(() => onStreamIdle(entry), streamIdleTimeoutMs),\n probeTimer: null,\n };\n refreshPendingTimeout(entry);\n routeEvent(serverPubkey, mcp);\n return;\n }\n /** Admit one fully validated frame into the shared progress sequence. */\n const admitOrderedActivity = (): boolean => {\n if (progress <= stream.lastProgress) {\n failStream(entry, 'non-monotonic progress');\n return false;\n }\n stream.lastProgress = progress;\n resetStreamIdleTimer(entry, stream);\n refreshPendingTimeout(entry);\n return true;\n };\n\n switch (cvm.frameType) {\n case 'start':\n // A second start on an already active stream MUST fail it (CEP-41).\n failStream(entry, 'duplicate start');\n return;\n case 'accept':\n if (!admitOrderedActivity()) return;\n routeEvent(serverPubkey, mcp);\n return;\n case 'chunk': {\n if (typeof cvm.data !== 'string' || !isBoundedInteger(cvm.chunkIndex, 0, Number.MAX_SAFE_INTEGER)) {\n failStream(entry, 'malformed chunk');\n return;\n }\n const index = cvm.chunkIndex;\n if (index < stream.nextChunkIndex) return; // duplicate: already delivered\n if (index > stream.nextChunkIndex) {\n // A higher chunkIndex may arrive first through a relay. Keep its\n // original progress so the sequence can be validated when the gap\n // closes; arrival order is not logical stream order.\n const existing = stream.outOfOrder.get(index);\n if (existing) {\n if (existing.progress !== progress || JSON.stringify(existing.message) !== JSON.stringify(mcp)) {\n failStream(entry, 'conflicting duplicate chunk');\n }\n return;\n }\n if (\n progress <= stream.lastProgress\n || stream.outOfOrder.size >= MAX_STREAM_BUFFERED_CHUNKS\n || stream.outOfOrderCodeUnits + cvm.data.length > MAX_STREAM_BUFFERED_CODE_UNITS\n ) {\n failStream(entry, progress <= stream.lastProgress ? 'non-monotonic progress' : 'gap buffer exhausted');\n return;\n }\n stream.outOfOrder.set(index, { message: mcp, progress, codeUnits: cvm.data.length });\n stream.outOfOrderCodeUnits += cvm.data.length;\n resetStreamIdleTimer(entry, stream);\n refreshPendingTimeout(entry);\n return;\n }\n\n // Build the newly contiguous run and validate its logical progress\n // before exposing any part of it to application handlers.\n const ready: Array<{ message: McpMessage; progress: number; codeUnits: number }> = [\n { message: mcp, progress, codeUnits: 0 },\n ];\n let nextIndex = index + 1;\n while (stream.outOfOrder.has(nextIndex)) {\n ready.push(stream.outOfOrder.get(nextIndex)!);\n nextIndex += 1;\n }\n let previousProgress = stream.lastProgress;\n for (const item of ready) {\n if (item.progress <= previousProgress) {\n failStream(entry, 'non-monotonic chunk order');\n return;\n }\n previousProgress = item.progress;\n }\n for (let bufferedIndex = index + 1; bufferedIndex < nextIndex; bufferedIndex++) {\n const buffered = stream.outOfOrder.get(bufferedIndex)!;\n stream.outOfOrder.delete(bufferedIndex);\n stream.outOfOrderCodeUnits -= buffered.codeUnits;\n }\n for (const item of ready) {\n routeEvent(serverPubkey, item.message);\n stream.nextChunkIndex += 1;\n }\n stream.lastProgress = previousProgress;\n resetStreamIdleTimer(entry, stream);\n refreshPendingTimeout(entry);\n return;\n }\n case 'ping': {\n // Invalid control frames are not activity and cannot extend deadlines.\n if (typeof cvm.nonce !== 'string') return;\n if (new TextEncoder().encode(cvm.nonce).byteLength > MAX_PING_NONCE_BYTES) return;\n if (!admitOrderedActivity()) return;\n void publishStreamFrame(entry, stream, { frameType: 'pong', nonce: cvm.nonce }).catch(() => {\n // A pong we cannot publish leaves the stream half-dead: fail it.\n failStream(entry, 'pong publication failed');\n });\n return;\n }\n case 'pong': {\n // Only a matching outstanding probe is valid liveness evidence.\n if (stream.pendingNonce === null || cvm.nonce !== stream.pendingNonce) return;\n if (!admitOrderedActivity()) return;\n stream.pendingNonce = null;\n if (stream.probeTimer) clearTimeout(stream.probeTimer);\n stream.probeTimer = null;\n return;\n }\n case 'close': {\n // A declared completeness bound MUST be fully satisfied: every\n // chunkIndex from 0 through lastChunkIndex received (CEP-41).\n if (cvm.lastChunkIndex !== undefined) {\n const bound = cvm.lastChunkIndex;\n const complete = isBoundedInteger(bound, 0, Number.MAX_SAFE_INTEGER)\n && stream.nextChunkIndex === bound + 1\n && stream.outOfOrder.size === 0;\n if (!complete) {\n failStream(entry, 'close with unresolved chunk gaps');\n return;\n }\n }\n if (!admitOrderedActivity()) return;\n routeEvent(serverPubkey, mcp);\n endStream(entry);\n return;\n }\n case 'abort':\n if (!admitOrderedActivity()) return;\n routeEvent(serverPubkey, mcp);\n endStream(entry);\n return;\n default:\n return; // malformed/unknown frames do not refresh liveness\n }\n }\n\n /** Extend a request's deadline while its admitted frames keep arriving. */\n function refreshPendingTimeout(entry: PendingRequest): void {\n clearTimeout(entry.timer);\n entry.timer = setTimeout(() => {\n const settled = settlePending(entry.correlationId);\n settled?.reject(new Error('relay timeout'));\n }, entry.timeoutMs);\n }\n\n /** Route a decrypted MCP message to a pending request or event handlers. */\n function routeMessage(serverPubkey: string, mcp: McpMessage): void {\n const id = mcp.id;\n if (id != null && pending.has(String(id))) {\n const entry = pending.get(String(id))!;\n if (entry.serverPubkey !== serverPubkey) return;\n const settled = settlePending(String(id));\n settled?.resolve({ ...mcp, id: entry.originalId });\n return;\n }\n routeEvent(serverPubkey, mcp);\n }\n\n /** Fan a server-initiated message out to CVM event handlers. */\n function routeEvent(serverPubkey: string, mcp: McpMessage): void {\n if (mcp.method !== undefined && sessions.has(serverPubkey)) {\n const server: CvmServerRef = { pubkey: serverPubkey };\n for (const handler of eventHandlers) handler(server, mcp);\n }\n }\n\n async function publishMcp(server: CvmServerRef, relays: string[], message: McpMessage): Promise<void> {\n // Capability discovery lives on the INNER signed kind-25910 event. Without\n // these single-element tags, muxll correctly assumes a legacy client and\n // sends large results as one giant NIP-44 payload (which exceeds the NIP-44\n // standard limit) instead of CEP-22 frames, and withholds CEP-41 streaming.\n const capabilityTags: string[][] = [\n ...(encrypt ? [[SUPPORT_ENCRYPTION]] : []),\n ...(encrypt && wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? [[SUPPORT_ENCRYPTION_EPHEMERAL]] : []),\n [SUPPORT_OVERSIZED_TRANSFER],\n [SUPPORT_OPEN_STREAM],\n ];\n const inner = finalizeEvent(\n {\n kind: KIND_CVM,\n created_at: Math.floor(Date.now() / 1000),\n tags: [['p', server.pubkey], ...capabilityTags],\n content: JSON.stringify(message),\n },\n clientSecretKey,\n ) as NostrEventLike;\n if (!encrypt) {\n await pool.publish(relays, inner);\n return;\n }\n const wrapSecretKey = generateSecretKey();\n const conversationKey = nip44.getConversationKey(wrapSecretKey, server.pubkey);\n // Ephemeral wraps (kind 21059) are not stored; relays reject backdated\n // ephemeral events as \"expired\", so they MUST carry a current timestamp.\n // Regular wraps (kind 1059) are backdated per NIP-59 to blur timing metadata.\n const createdAt =\n wrapKind === KIND_GIFT_WRAP_EPHEMERAL ? Math.floor(Date.now() / 1000) : randomizedPastTimestamp();\n const wrap = finalizeEvent(\n {\n kind: wrapKind,\n created_at: createdAt,\n tags: [['p', server.pubkey]],\n content: nip44.encrypt(JSON.stringify(inner), conversationKey),\n },\n wrapSecretKey,\n ) as NostrEventLike;\n await pool.publish(relays, wrap);\n }\n\n function sendCorrelated(\n server: CvmServerRef,\n relays: string[],\n message: McpMessage,\n timeout: number,\n ): Promise<McpMessage> {\n const correlationId = nextCorrelationId();\n const originalId = message.id;\n let outgoing: McpMessage = { ...message, id: correlationId };\n let issuedToken: string | number | undefined;\n\n // CEP-22 and CEP-41 address progress frames by params._meta.progressToken.\n // Official ContextVM clients add one automatically to every tools/call;\n // Paja's hand-rolled transport must do the same. Preserve an explicit token\n // supplied by a streaming caller; otherwise use the unique correlation id.\n const params = message.params && typeof message.params === 'object'\n ? message.params as Record<string, unknown>\n : undefined;\n const meta = params?._meta && typeof params._meta === 'object'\n ? params._meta as Record<string, unknown>\n : undefined;\n const explicit = meta?.progressToken;\n if (message.method === 'tools/call') {\n issuedToken = typeof explicit === 'string' || typeof explicit === 'number'\n ? explicit\n : correlationId;\n outgoing = {\n ...outgoing,\n params: {\n ...params,\n _meta: {\n ...meta,\n progressToken: issuedToken,\n },\n },\n };\n } else if (typeof explicit === 'string' || typeof explicit === 'number') {\n // An explicit token on any other method is bound for frame admission\n // exactly as sent; automatic injection stays tools/call-only.\n issuedToken = explicit;\n }\n\n // The token binds inbound frames to this request and its expected server.\n // Reject a duplicate explicit token to the same server while the previous\n // request is still in flight: CEP-41 requires distinct tokens per stream,\n // and a shared one would make frame admission ambiguous.\n const tokenKey = progressTokenKey(issuedToken);\n const issuedKey = tokenKey ? `${server.pubkey}:${tokenKey}` : null;\n if (issuedKey && issuedTokens.has(issuedKey)) {\n return Promise.reject(new Error('duplicate progress token'));\n }\n\n return new Promise<McpMessage>((resolve, reject) => {\n const entry: PendingRequest = {\n resolve,\n reject,\n timer: setTimeout(() => {\n const settled = settlePending(correlationId);\n settled?.reject(new Error('relay timeout'));\n }, timeout),\n timeoutMs: timeout,\n originalId,\n serverPubkey: server.pubkey,\n correlationId,\n };\n if (issuedKey) {\n entry.tokenKey = issuedKey;\n issuedTokens.set(issuedKey, correlationId);\n }\n pending.set(correlationId, entry);\n void publishMcp(server, relays, outgoing).catch((err: unknown) => {\n const settled = settlePending(correlationId);\n settled?.reject(err instanceof Error ? err : new Error('publish failed'));\n });\n });\n }\n\n function getSession(server: CvmServerRef): ServerSession {\n let session = sessions.get(server.pubkey);\n if (!session) {\n const relays = resolveRelays(server);\n session = { relays, initialized: false, initializing: null };\n sessions.set(server.pubkey, session);\n holdRelays(relays);\n }\n return session;\n }\n\n async function ensureInitialized(server: CvmServerRef, session: ServerSession, timeout: number): Promise<void> {\n if (session.initialized) return;\n if (session.initializing) return session.initializing;\n session.initializing = (async () => {\n try {\n await sendCorrelated(\n server,\n session.relays,\n {\n jsonrpc: '2.0',\n id: 'init',\n method: 'initialize',\n params: { protocolVersion: MCP_PROTOCOL_VERSION, capabilities: {}, clientInfo },\n },\n timeout,\n );\n // notifications/initialized completes the handshake; no response expected.\n await publishMcp(server, session.relays, { jsonrpc: '2.0', method: 'notifications/initialized' });\n session.initialized = true;\n } catch {\n throw new Error('initialization failed');\n } finally {\n session.initializing = null;\n }\n })();\n return session.initializing;\n }\n\n return {\n async discover(query?: CvmDiscoverQuery): Promise<CvmServer[]> {\n const relays = query?.relays && query.relays.length > 0 ? query.relays : defaultRelays;\n if (relays.length === 0) return [];\n const announces = new Map<string, NostrEventLike>();\n const toolLists = new Map<string, NostrEventLike>();\n await new Promise<void>((resolve) => {\n const sub = pool.subscribe(\n relays,\n { kinds: [KIND_ANNOUNCE_SERVER, KIND_ANNOUNCE_TOOLS], limit: query?.limit ? query.limit * 4 : 100 },\n {\n onevent(event) {\n if (event.kind === KIND_ANNOUNCE_SERVER) announces.set(event.pubkey, event);\n else if (event.kind === KIND_ANNOUNCE_TOOLS) toolLists.set(event.pubkey, event);\n },\n oneose() { resolve(); },\n },\n );\n setTimeout(() => { sub.close(); resolve(); }, DEFAULT_DISCOVER_TIMEOUT_MS);\n });\n\n const servers: CvmServer[] = [];\n for (const [pubkey, event] of announces) {\n const name = tagValue(event.tags, 'name');\n const description = tagValue(event.tags, 'about');\n const server: CvmServer = {\n pubkey,\n relays: [...relays],\n ...(name ? { name } : {}),\n ...(description ? { description } : {}),\n paymentRequired: false,\n };\n const tools = toolLists.get(pubkey);\n if (tools) {\n const names = tools.tags.filter((tag) => tag[0] === 'i' && typeof tag[2] === 'string').map((tag) => tag[2]);\n if (names.length > 0) server.capabilities = names;\n }\n servers.push(server);\n }\n\n const search = query?.search?.toLowerCase();\n const filtered = search\n ? servers.filter((s) => `${s.name ?? ''} ${s.description ?? ''}`.toLowerCase().includes(search))\n : servers;\n return query?.limit ? filtered.slice(0, query.limit) : filtered;\n },\n\n async request(server: CvmServerRef, message: McpMessage, requestOptions?: CvmRequestOptions): Promise<McpMessage> {\n const session = getSession(server);\n const timeout = requestOptions?.timeoutMs ?? timeoutMs;\n if (requestOptions?.initialize) await ensureInitialized(server, session, timeout);\n return sendCorrelated(server, session.relays, message, timeout);\n },\n\n async close(server: CvmServerRef): Promise<void> {\n const session = sessions.get(server.pubkey);\n if (!session) return;\n sessions.delete(server.pubkey);\n releaseRelays(session.relays);\n // Settle this server's in-flight requests and release their frame\n // state: a closed server's tokens must stop admitting frames on a\n // shared relay (NAP-CVM: close releases pending correlation records).\n for (const [correlationId, entry] of pending) {\n if (entry.serverPubkey !== server.pubkey) continue;\n settlePending(correlationId)?.reject(new Error('server closed'));\n }\n },\n\n onEvent(handler: EventHandler): { close(): void } {\n eventHandlers.add(handler);\n return {\n close() {\n eventHandlers.delete(handler);\n },\n };\n },\n\n dispose(): void {\n inbound?.close();\n inbound = null;\n for (const entry of pending.values()) {\n clearTimeout(entry.timer);\n releaseTransfer(entry);\n releaseStream(entry);\n entry.reject(new Error('transport disposed'));\n }\n pending.clear();\n issuedTokens.clear();\n sessions.clear();\n relayRefcount.clear();\n eventHandlers.clear();\n subscribedRelays = '';\n },\n };\n}\n"],"mappings":"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","names":["encrypt"]}
|
package/package.json
CHANGED