@kehto/services 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -9
- package/dist/cvm-nostr-transport.d.ts +2 -2
- package/dist/cvm-nostr-transport.js +17 -12
- package/dist/cvm-nostr-transport.js.map +1 -1
- package/dist/{cvm-service-DKIzbiDw.d.ts → cvm-service-t5syG4tU.d.ts} +2 -0
- package/dist/index.d.ts +197 -213
- package/dist/index.js +1195 -366
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,9 +17,11 @@ pnpm add @kehto/services
|
|
|
17
17
|
## Published Napplet Compatibility
|
|
18
18
|
|
|
19
19
|
`@kehto/services` publishes against `@napplet/core` and `@napplet/nap`
|
|
20
|
-
`>=0.31.0 <0.32.0`. The exact installed contracts are core/nap 0.31.
|
|
21
|
-
|
|
22
|
-
`
|
|
20
|
+
`>=0.31.0 <0.32.0`. The exact installed contracts are core 0.31.1 / nap 0.31.2
|
|
21
|
+
from NAP-INTENT authority `5ac0490461ca6fec2f0d2e45b4835cf9bc08de24`,
|
|
22
|
+
napplet/web#199 source `3037200c932488f14f7f369b8583c39c9c16510a` / merge
|
|
23
|
+
`b3f0007867eac109fa4917fac9c285d3b7cc6155`, and Version Packages #198 release
|
|
24
|
+
source `dc1d24153c759152b6ba31a6ec9bea967798f2df`.
|
|
23
25
|
|
|
24
26
|
## Overview
|
|
25
27
|
|
|
@@ -42,10 +44,11 @@ Current draft posture:
|
|
|
42
44
|
- `createNotifyService` handles direct `notify.*` envelopes. It is not an INC
|
|
43
45
|
topic handler.
|
|
44
46
|
- `createOutboxService` supports `outbox.getEvent` from draft NAP-OUTBOX. Single-event lookups run through shell-owned relay routing and only return events whose ID matches the request. The draft wire contract keeps `outbox.query` one-shot and `outbox.subscribe` streaming; Kehto's concrete `createRelayPoolOutboxRouter` additionally exposes host-side `queryStream()` so verified query events can arrive before asynchronous NIP-65 discovery completes.
|
|
45
|
-
- `createResourceService`
|
|
47
|
+
- `createResourceService` implements the draft NAP-RESOURCE request lifecycle and current result shapes. It fail-closes when no scheme is configured, checks per-identity origin grants, enforces disclosed size/bulk caps, scopes cancellation to the requesting window, drops cancelled terminal envelopes, and never forwards response headers. The host policy fetch must return byte-classified output and owns scheme-specific SSRF, redirect, integrity, and SVG handling.
|
|
46
48
|
- `createUploadService` supports `upload.info` from draft NAP-UPLOAD. Hosts may expose configured rails, return URL forms, maximum bytes, and MIME type policy without requiring napplets to start an upload.
|
|
47
|
-
- `
|
|
48
|
-
- `createDmService` keeps NAP-DM request correlation, subscriptions, and message
|
|
49
|
+
- `createConfigService` implements draft NAP-CONFIG's shell-writer boundary: recursive bounded-subset schema validation, per-window schema/subscription state, deterministic defaults, invalid/orphan removal, version rollback rejection, scoped host persistence hooks, and settings-UI commits. Reads before schema registration fail closed with `no-schema`.
|
|
50
|
+
- `createDmService` keeps NAP-DM request correlation, per-window subscriptions, and packaged message shapes in runtime-owned code. Adapters cover concrete transports: verified NIP-17 gift wraps and relay history via `nostr-tools`, structural NDR runtimes with relay hooks, and Cordn/ContextVM coordinator clients.
|
|
51
|
+
- `createFsService` keeps NAP-FS result correlation and per-window watch identity in runtime-owned code. An injected `FsBackend` owns real persistence, virtual-path policy, picker mediation, byte validation, revisions, mutations, and watch observation.
|
|
49
52
|
|
|
50
53
|
## NAP-INTENT manifest resolver
|
|
51
54
|
|
|
@@ -106,7 +109,16 @@ runtime.registerService(
|
|
|
106
109
|
// Notification service — direct NIP-5D notify.* envelopes.
|
|
107
110
|
runtime.registerService(
|
|
108
111
|
'notify',
|
|
109
|
-
createNotifyService({
|
|
112
|
+
createNotifyService({
|
|
113
|
+
present: ({ notificationId, message, emit }) => notificationCenter.show({
|
|
114
|
+
notificationId,
|
|
115
|
+
title: message.title,
|
|
116
|
+
body: message.body,
|
|
117
|
+
onClick: () => emit({ type: 'notify.clicked', notificationId }),
|
|
118
|
+
}),
|
|
119
|
+
dismiss: (_windowId, notificationId) => notificationCenter.dismiss(notificationId),
|
|
120
|
+
requestPermission: (_windowId, channel) => notificationPolicy.request(channel),
|
|
121
|
+
}),
|
|
110
122
|
);
|
|
111
123
|
|
|
112
124
|
// Lists service — shell-owned NIP-51 metadata and mutations.
|
|
@@ -516,7 +528,10 @@ Each factory returns a `ServiceHandler` registrable via `runtime.registerService
|
|
|
516
528
|
- `createRelayPoolService` — `relay.publish`, `relay.publishEncrypted`,
|
|
517
529
|
`relay.subscribe` fan-out (`relay:read` / `relay:write`). Publish handlers
|
|
518
530
|
receive only runtime-signed events and settle with canonical
|
|
519
|
-
`{ ok, event, eventId }` or `{ ok: false, error }` result envelopes.
|
|
531
|
+
`{ ok, event, eventId }` or `{ ok: false, error }` result envelopes. Subscribe
|
|
532
|
+
adapters may pass observed relay URLs as the callback's second argument so
|
|
533
|
+
`RelayEventResult.sidecar.relayHints` records provenance rather than the
|
|
534
|
+
larger requested relay set.
|
|
520
535
|
- `createCacheService` — offline event cache (`cache:read` / `cache:write`).
|
|
521
536
|
- `createCoordinatedRelay` — composite service that bundles relay-pool + cache
|
|
522
537
|
with read-through behavior and the same canonical publish result.
|
|
@@ -539,6 +554,10 @@ Each factory returns a `ServiceHandler` registrable via `runtime.registerService
|
|
|
539
554
|
- `createNdrDmAdapter` / `createNdrRelayTransport` — structural adapter for Nostr Double Ratchet runtimes plus a relay transport bridge for app-owned `nostrSubscribe` / `nostrFetch` / `nostrPublish` hooks.
|
|
540
555
|
- `createCordnDmAdapter` / `createCordnRelayCoordinatorClient` — structural adapter for Cordn/ContextVM clients plus a relay-backed coordinator bridge for `PostGroupMessage`, `FetchGroupMessages`, and `SubscribeGroupMessages`.
|
|
541
556
|
|
|
557
|
+
### FS NAP
|
|
558
|
+
- `createFsService` — all `fs.*` request/result envelopes plus scoped `fs.changed` pushes. It guarantees same-id results, error/success-field exclusivity, cross-window watch isolation, and window-destroy cleanup.
|
|
559
|
+
- `FsBackend` — host contract for runtime-owned virtual paths, real picker/storage handles, bounded range I/O, atomic writes, directory mutation, and advisory watches. `FsServiceError` maps failures to the closed NAP-FS error set.
|
|
560
|
+
|
|
542
561
|
### Theme NAP
|
|
543
562
|
- `createThemeService` — `theme.get` plus automatic `theme.changed` delivery
|
|
544
563
|
(`theme:read`). Returns a `ThemeService` with `publishTheme()` and
|
|
@@ -572,7 +591,7 @@ complete normal result without `error`: this explicit policy reconciles the
|
|
|
572
591
|
draft error-only example without a mixed theme/error extension.
|
|
573
592
|
|
|
574
593
|
### Types
|
|
575
|
-
`AudioSource`, `AudioServiceOptions`, `Notification`, `NotificationServiceOptions`, `IdentityServiceOptions`, `RelayPoolServiceOptions`, `CacheServiceOptions`, `CoordinatedRelayOptions`, `KeysServiceOptions`, `MediaServiceOptions`, `NotifyServiceOptions`, `ThemeServiceOptions`, `ThemeService`, `IntentOpenOptions`, `IntentRequest`, `IntentResult`, `IntentCandidate`, `IntentAvailability`, `IntentResolver`, `IntentTargetController`, `IntentDispatchParams`, `IntentTargetDispatch`, `BleServiceOptions`, `BleServiceContext`, `WebrtcServiceOptions`, `WebrtcServiceContext`, `DmServiceOptions`, `DmAdapter`, `DmRelayPool`, `Nip17DmAdapterOptions`, `NdrDmAdapterOptions`, `CordnDmAdapterOptions`.
|
|
594
|
+
`AudioSource`, `AudioServiceOptions`, `Notification`, `NotificationServiceOptions`, `IdentityServiceOptions`, `RelayPoolServiceOptions`, `CacheServiceOptions`, `CoordinatedRelayOptions`, `KeysServiceOptions`, `MediaServiceOptions`, `NotifyServiceOptions`, `NotifyPresentation`, `NotifyInteractionMessage`, `ThemeServiceOptions`, `ThemeService`, `IntentOpenOptions`, `IntentRequest`, `IntentResult`, `IntentCandidate`, `IntentAvailability`, `IntentResolver`, `IntentTargetController`, `IntentDispatchParams`, `IntentTargetDispatch`, `BleServiceOptions`, `BleServiceContext`, `WebrtcServiceOptions`, `WebrtcServiceContext`, `DmServiceOptions`, `DmAdapter`, `DmRelayPool`, `Nip17DmAdapterOptions`, `NdrDmAdapterOptions`, `CordnDmAdapterOptions`.
|
|
576
595
|
|
|
577
596
|
## API Reference
|
|
578
597
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as CvmTransport } from './cvm-service-
|
|
1
|
+
import { C as CvmTransport } from './cvm-service-t5syG4tU.js';
|
|
2
2
|
import '@kehto/runtime';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -63,7 +63,7 @@ interface CvmRelayPool {
|
|
|
63
63
|
onevent?: (event: NostrEventLike) => void;
|
|
64
64
|
oneose?: () => void;
|
|
65
65
|
}): CvmSubCloser;
|
|
66
|
-
publish(relays: string[], event: NostrEventLike): unknown
|
|
66
|
+
publish(relays: string[], event: NostrEventLike): void | Promise<unknown>;
|
|
67
67
|
}
|
|
68
68
|
/** Options for {@link createNostrCvmTransport}. */
|
|
69
69
|
interface NostrCvmTransportOptions {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cvm-nostr-transport.ts
|
|
2
2
|
import { SimplePool } from "nostr-tools/pool";
|
|
3
|
-
import { finalizeEvent, generateSecretKey, getPublicKey } from "nostr-tools/pure";
|
|
3
|
+
import { finalizeEvent, generateSecretKey, getPublicKey, verifyEvent } from "nostr-tools/pure";
|
|
4
4
|
import * as nip44 from "nostr-tools/nip44";
|
|
5
5
|
var KIND_CVM = 25910;
|
|
6
6
|
var KIND_GIFT_WRAP_EPHEMERAL = 21059;
|
|
@@ -31,8 +31,10 @@ function simplePoolAdapter(sp) {
|
|
|
31
31
|
oneose: params.oneose
|
|
32
32
|
});
|
|
33
33
|
},
|
|
34
|
-
publish(relays, event) {
|
|
35
|
-
|
|
34
|
+
async publish(relays, event) {
|
|
35
|
+
const attempts = sp.publish(relays, event);
|
|
36
|
+
if (attempts.length === 0) throw new Error("server not found");
|
|
37
|
+
await Promise.any(attempts);
|
|
36
38
|
}
|
|
37
39
|
};
|
|
38
40
|
}
|
|
@@ -91,6 +93,8 @@ function createNostrCvmTransport(options = {}) {
|
|
|
91
93
|
return true;
|
|
92
94
|
}
|
|
93
95
|
function handleInbound(event) {
|
|
96
|
+
if (!verifyEvent(event)) return;
|
|
97
|
+
if (!event.tags.some((tag) => tag[0] === "p" && tag[1] === clientPubkey)) return;
|
|
94
98
|
if (!rememberWrap(event.id)) return;
|
|
95
99
|
let serverPubkey;
|
|
96
100
|
let mcp;
|
|
@@ -98,6 +102,8 @@ function createNostrCvmTransport(options = {}) {
|
|
|
98
102
|
if (encrypt2) {
|
|
99
103
|
const conversationKey = nip44.getConversationKey(clientSecretKey, event.pubkey);
|
|
100
104
|
const inner = JSON.parse(nip44.decrypt(event.content, conversationKey));
|
|
105
|
+
if (!verifyEvent(inner)) return;
|
|
106
|
+
if (!inner.tags.some((tag) => tag[0] === "p" && tag[1] === clientPubkey)) return;
|
|
101
107
|
serverPubkey = inner.pubkey;
|
|
102
108
|
mcp = JSON.parse(inner.content);
|
|
103
109
|
} else {
|
|
@@ -110,6 +116,7 @@ function createNostrCvmTransport(options = {}) {
|
|
|
110
116
|
const id = mcp.id;
|
|
111
117
|
if (id != null && pending.has(String(id))) {
|
|
112
118
|
const entry = pending.get(String(id));
|
|
119
|
+
if (entry.serverPubkey !== serverPubkey) return;
|
|
113
120
|
pending.delete(String(id));
|
|
114
121
|
clearTimeout(entry.timer);
|
|
115
122
|
entry.resolve({ ...mcp, id: entry.originalId });
|
|
@@ -120,13 +127,13 @@ function createNostrCvmTransport(options = {}) {
|
|
|
120
127
|
for (const handler of eventHandlers) handler(server, mcp);
|
|
121
128
|
}
|
|
122
129
|
}
|
|
123
|
-
function publishMcp(server, relays, message) {
|
|
130
|
+
async function publishMcp(server, relays, message) {
|
|
124
131
|
const inner = finalizeEvent(
|
|
125
132
|
{ kind: KIND_CVM, created_at: Math.floor(Date.now() / 1e3), tags: [["p", server.pubkey]], content: JSON.stringify(message) },
|
|
126
133
|
clientSecretKey
|
|
127
134
|
);
|
|
128
135
|
if (!encrypt2) {
|
|
129
|
-
pool.publish(relays, inner);
|
|
136
|
+
await pool.publish(relays, inner);
|
|
130
137
|
return;
|
|
131
138
|
}
|
|
132
139
|
const wrapSecretKey = generateSecretKey();
|
|
@@ -141,7 +148,7 @@ function createNostrCvmTransport(options = {}) {
|
|
|
141
148
|
},
|
|
142
149
|
wrapSecretKey
|
|
143
150
|
);
|
|
144
|
-
pool.publish(relays, wrap);
|
|
151
|
+
await pool.publish(relays, wrap);
|
|
145
152
|
}
|
|
146
153
|
function sendCorrelated(server, relays, message, timeout) {
|
|
147
154
|
const correlationId = nextCorrelationId();
|
|
@@ -152,14 +159,12 @@ function createNostrCvmTransport(options = {}) {
|
|
|
152
159
|
pending.delete(correlationId);
|
|
153
160
|
reject(new Error("relay timeout"));
|
|
154
161
|
}, timeout);
|
|
155
|
-
pending.set(correlationId, { resolve, reject, timer, originalId });
|
|
156
|
-
|
|
157
|
-
publishMcp(server, relays, outgoing);
|
|
158
|
-
} catch (err) {
|
|
162
|
+
pending.set(correlationId, { resolve, reject, timer, originalId, serverPubkey: server.pubkey });
|
|
163
|
+
void publishMcp(server, relays, outgoing).catch((err) => {
|
|
159
164
|
pending.delete(correlationId);
|
|
160
165
|
clearTimeout(timer);
|
|
161
166
|
reject(err instanceof Error ? err : new Error("publish failed"));
|
|
162
|
-
}
|
|
167
|
+
});
|
|
163
168
|
});
|
|
164
169
|
}
|
|
165
170
|
function getSession(server) {
|
|
@@ -188,7 +193,7 @@ function createNostrCvmTransport(options = {}) {
|
|
|
188
193
|
},
|
|
189
194
|
timeout
|
|
190
195
|
);
|
|
191
|
-
publishMcp(server, session.relays, { jsonrpc: "2.0", method: "notifications/initialized" });
|
|
196
|
+
await publishMcp(server, session.relays, { jsonrpc: "2.0", method: "notifications/initialized" });
|
|
192
197
|
session.initialized = true;
|
|
193
198
|
} catch {
|
|
194
199
|
throw new Error("initialization failed");
|
|
@@ -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 } 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): 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}\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 publish(relays, event) {\n return sp.publish(relays, event as NostrToolsEvent);\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 (!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 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 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 function publishMcp(server: CvmServerRef, relays: string[], message: McpMessage): 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 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 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 });\n try {\n publishMcp(server, relays, outgoing);\n } catch (err) {\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 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 *\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":";AA+BA,SAAS,kBAAkB;AAC3B,SAAS,eAAe,mBAAmB,cAAc,mBAAmB;AAC5E,YAAY,WAAW;AAavB,IAAM,WAAW;AAEjB,IAAM,2BAA2B;AACjC,IAAM,yBAAyB;AAE/B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB;AAC3B,IAAM,8BAA8B;AACpC,IAAM,uBAAuB;AAC7B,IAAM,kBAAkB;AA4ExB,IAAI,qBAAqB;AACzB,SAAS,oBAA4B;AACnC,wBAAsB;AACtB,SAAO,OAAO,kBAAkB,IAAI,aAAa,kBAAkB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;AACnF;AAEA,SAAS,0BAAkC;AAEzC,QAAM,SAAS,KAAK,MAAM,KAAK,OAAO,IAAI,MAAO;AACjD,SAAO,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;AACzC;AAEA,SAAS,SAAS,MAAkB,MAAkC;AACpE,SAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC;AAChD;AAGA,SAAS,kBAAkB,IAA8B;AACvD,SAAO;AAAA,IACL,UAAU,QAAQ,QAAQ,QAAQ;AAChC,aAAO,GAAG,UAAU,QAAQ,QAA4B;AAAA,QACtD,SAAS,OAAO;AAAA,QAChB,QAAQ,OAAO;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO;AAC3B,YAAM,WAAW,GAAG,QAAQ,QAAQ,KAAwB;AAC5D,UAAI,SAAS,WAAW,EAAG,OAAM,IAAI,MAAM,kBAAkB;AAC7D,YAAM,QAAQ,IAAI,QAAQ;AAAA,IAC5B;AAAA,EACF;AACF;AASO,SAAS,wBACd,UAAoC,CAAC,GACD;AACpC,QAAM,gBAAgB,QAAQ,iBAAiB,CAAC;AAChD,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAMA,WAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,kBAAkB,QAAQ,yBAAyB;AAC5E,QAAM,OAAqB,QAAQ,QAAQ,kBAAkB,IAAI,WAAW,CAAC;AAC7E,QAAM,kBAAkB,QAAQ,mBAAmB,kBAAkB;AACrE,QAAM,eAAe,aAAa,eAAe;AACjD,QAAM,aAAa,QAAQ,cAAc,EAAE,MAAM,aAAa,SAAS,QAAQ;AAE/E,QAAM,WAAW,oBAAI,IAA2B;AAChD,QAAM,UAAU,oBAAI,IAA4B;AAChD,QAAM,gBAAgB,oBAAI,IAAkB;AAC5C,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,QAAM,YAAY,oBAAI,IAAY;AAClC,MAAI,UAA+B;AACnC,MAAI,mBAAmB;AAEvB,WAAS,cAAc,QAAgC;AACrD,UAAM,SAAS,OAAO,UAAU,OAAO,OAAO,SAAS,IAAI,OAAO,SAAS;AAC3E,QAAI,OAAO,WAAW,EAAG,OAAM,IAAI,MAAM,kBAAkB;AAC3D,WAAO,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;AAAA,EAC5B;AAEA,WAAS,WAAW,QAAwB;AAC1C,eAAW,OAAO,OAAQ,eAAc,IAAI,MAAM,cAAc,IAAI,GAAG,KAAK,KAAK,CAAC;AAClF,wBAAoB;AAAA,EACtB;AAEA,WAAS,cAAc,QAAwB;AAC7C,eAAW,OAAO,QAAQ;AACxB,YAAM,SAAS,cAAc,IAAI,GAAG,KAAK,KAAK;AAC9C,UAAI,SAAS,EAAG,eAAc,OAAO,GAAG;AAAA,UACnC,eAAc,IAAI,KAAK,KAAK;AAAA,IACnC;AACA,wBAAoB;AAAA,EACtB;AAEA,WAAS,sBAA4B;AACnC,UAAM,SAAS,CAAC,GAAG,cAAc,KAAK,CAAC,EAAE,KAAK;AAC9C,UAAM,MAAM,OAAO,KAAK,GAAG;AAC3B,QAAI,QAAQ,iBAAkB;AAC9B,aAAS,MAAM;AACf,uBAAmB;AACnB,cAAU,OAAO,WAAW,IACxB,OACA,KAAK;AAAA,MACH;AAAA,MACA,EAAE,OAAOA,WAAU,CAAC,wBAAwB,wBAAwB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE;AAAA,MAC3G,EAAE,SAAS,cAAc;AAAA,IAC3B;AAAA,EACN;AAEA,WAAS,aAAa,IAAqB;AACzC,QAAI,UAAU,IAAI,EAAE,EAAG,QAAO;AAC9B,cAAU,IAAI,EAAE;AAChB,QAAI,UAAU,OAAO,iBAAiB;AACpC,YAAM,SAAS,UAAU,OAAO,EAAE,KAAK,EAAE;AACzC,UAAI,WAAW,OAAW,WAAU,OAAO,MAAM;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAA6B;AAClD,QAAI,CAAC,YAAY,KAAwB,EAAG;AAC5C,QAAI,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,YAAY,EAAG;AAC1E,QAAI,CAAC,aAAa,MAAM,EAAE,EAAG;AAC7B,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,UAAIA,UAAS;AACX,cAAM,kBAAwB,yBAAmB,iBAAiB,MAAM,MAAM;AAC9E,cAAM,QAAQ,KAAK,MAAY,cAAQ,MAAM,SAAS,eAAe,CAAC;AACtE,YAAI,CAAC,YAAY,KAAwB,EAAG;AAC5C,YAAI,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,YAAY,EAAG;AAC1E,uBAAe,MAAM;AACrB,cAAM,KAAK,MAAM,MAAM,OAAO;AAAA,MAChC,OAAO;AACL,uBAAe,MAAM;AACrB,cAAM,KAAK,MAAM,MAAM,OAAO;AAAA,MAChC;AAAA,IACF,QAAQ;AACN;AAAA,IACF;AAEA,UAAM,KAAK,IAAI;AACf,QAAI,MAAM,QAAQ,QAAQ,IAAI,OAAO,EAAE,CAAC,GAAG;AACzC,YAAM,QAAQ,QAAQ,IAAI,OAAO,EAAE,CAAC;AACpC,UAAI,MAAM,iBAAiB,aAAc;AACzC,cAAQ,OAAO,OAAO,EAAE,CAAC;AACzB,mBAAa,MAAM,KAAK;AACxB,YAAM,QAAQ,EAAE,GAAG,KAAK,IAAI,MAAM,WAAW,CAAC;AAC9C;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,UAAa,SAAS,IAAI,YAAY,GAAG;AAC1D,YAAM,SAAuB,EAAE,QAAQ,aAAa;AACpD,iBAAW,WAAW,cAAe,SAAQ,QAAQ,GAAG;AAAA,IAC1D;AAAA,EACF;AAEA,iBAAe,WAAW,QAAsB,QAAkB,SAAoC;AACpG,UAAM,QAAQ;AAAA,MACZ,EAAE,MAAM,UAAU,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,GAAG,MAAM,CAAC,CAAC,KAAK,OAAO,MAAM,CAAC,GAAG,SAAS,KAAK,UAAU,OAAO,EAAE;AAAA,MAC5H;AAAA,IACF;AACA,QAAI,CAACA,UAAS;AACZ,YAAM,KAAK,QAAQ,QAAQ,KAAK;AAChC;AAAA,IACF;AACA,UAAM,gBAAgB,kBAAkB;AACxC,UAAM,kBAAwB,yBAAmB,eAAe,OAAO,MAAM;AAI7E,UAAM,YACJ,aAAa,2BAA2B,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,wBAAwB;AAClG,UAAM,OAAO;AAAA,MACX;AAAA,QACE,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,MAAM,CAAC,CAAC,KAAK,OAAO,MAAM,CAAC;AAAA,QAC3B,SAAe,cAAQ,KAAK,UAAU,KAAK,GAAG,eAAe;AAAA,MAC/D;AAAA,MACA;AAAA,IACF;AACA,UAAM,KAAK,QAAQ,QAAQ,IAAI;AAAA,EACjC;AAEA,WAAS,eACP,QACA,QACA,SACA,SACqB;AACrB,UAAM,gBAAgB,kBAAkB;AACxC,UAAM,aAAa,QAAQ;AAC3B,UAAM,WAAuB,EAAE,GAAG,SAAS,IAAI,cAAc;AAC7D,WAAO,IAAI,QAAoB,CAAC,SAAS,WAAW;AAClD,YAAM,QAAQ,WAAW,MAAM;AAC7B,gBAAQ,OAAO,aAAa;AAC5B,eAAO,IAAI,MAAM,eAAe,CAAC;AAAA,MACnC,GAAG,OAAO;AACV,cAAQ,IAAI,eAAe,EAAE,SAAS,QAAQ,OAAO,YAAY,cAAc,OAAO,OAAO,CAAC;AAC9F,WAAK,WAAW,QAAQ,QAAQ,QAAQ,EAAE,MAAM,CAAC,QAAiB;AAChE,gBAAQ,OAAO,aAAa;AAC5B,qBAAa,KAAK;AAClB,eAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,gBAAgB,CAAC;AAAA,MACjE,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,WAAS,WAAW,QAAqC;AACvD,QAAI,UAAU,SAAS,IAAI,OAAO,MAAM;AACxC,QAAI,CAAC,SAAS;AACZ,YAAM,SAAS,cAAc,MAAM;AACnC,gBAAU,EAAE,QAAQ,aAAa,OAAO,cAAc,KAAK;AAC3D,eAAS,IAAI,OAAO,QAAQ,OAAO;AACnC,iBAAW,MAAM;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,kBAAkB,QAAsB,SAAwB,SAAgC;AAC7G,QAAI,QAAQ,YAAa;AACzB,QAAI,QAAQ,aAAc,QAAO,QAAQ;AACzC,YAAQ,gBAAgB,YAAY;AAClC,UAAI;AACF,cAAM;AAAA,UACJ;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,YACE,SAAS;AAAA,YACT,IAAI;AAAA,YACJ,QAAQ;AAAA,YACR,QAAQ,EAAE,iBAAiB,sBAAsB,cAAc,CAAC,GAAG,WAAW;AAAA,UAChF;AAAA,UACA;AAAA,QACF;AAEA,cAAM,WAAW,QAAQ,QAAQ,QAAQ,EAAE,SAAS,OAAO,QAAQ,4BAA4B,CAAC;AAChG,gBAAQ,cAAc;AAAA,MACxB,QAAQ;AACN,cAAM,IAAI,MAAM,uBAAuB;AAAA,MACzC,UAAE;AACA,gBAAQ,eAAe;AAAA,MACzB;AAAA,IACF,GAAG;AACH,WAAO,QAAQ;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,OAAgD;AAC7D,YAAM,SAAS,OAAO,UAAU,MAAM,OAAO,SAAS,IAAI,MAAM,SAAS;AACzE,UAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AACjC,YAAM,YAAY,oBAAI,IAA4B;AAClD,YAAM,YAAY,oBAAI,IAA4B;AAClD,YAAM,IAAI,QAAc,CAAC,YAAY;AACnC,cAAM,MAAM,KAAK;AAAA,UACf;AAAA,UACA,EAAE,OAAO,CAAC,sBAAsB,mBAAmB,GAAG,OAAO,OAAO,QAAQ,MAAM,QAAQ,IAAI,IAAI;AAAA,UAClG;AAAA,YACE,QAAQ,OAAO;AACb,kBAAI,MAAM,SAAS,qBAAsB,WAAU,IAAI,MAAM,QAAQ,KAAK;AAAA,uBACjE,MAAM,SAAS,oBAAqB,WAAU,IAAI,MAAM,QAAQ,KAAK;AAAA,YAChF;AAAA,YACA,SAAS;AAAE,sBAAQ;AAAA,YAAG;AAAA,UACxB;AAAA,QACF;AACA,mBAAW,MAAM;AAAE,cAAI,MAAM;AAAG,kBAAQ;AAAA,QAAG,GAAG,2BAA2B;AAAA,MAC3E,CAAC;AAED,YAAM,UAAuB,CAAC;AAC9B,iBAAW,CAAC,QAAQ,KAAK,KAAK,WAAW;AACvC,cAAM,OAAO,SAAS,MAAM,MAAM,MAAM;AACxC,cAAM,cAAc,SAAS,MAAM,MAAM,OAAO;AAChD,cAAM,SAAoB;AAAA,UACxB;AAAA,UACA,QAAQ,CAAC,GAAG,MAAM;AAAA,UAClB,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,UACvB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,UACrC,iBAAiB;AAAA,QACnB;AACA,cAAM,QAAQ,UAAU,IAAI,MAAM;AAClC,YAAI,OAAO;AACT,gBAAM,QAAQ,MAAM,KAAK,OAAO,CAAC,QAAQ,IAAI,CAAC,MAAM,OAAO,OAAO,IAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;AAC1G,cAAI,MAAM,SAAS,EAAG,QAAO,eAAe;AAAA,QAC9C;AACA,gBAAQ,KAAK,MAAM;AAAA,MACrB;AAEA,YAAM,SAAS,OAAO,QAAQ,YAAY;AAC1C,YAAM,WAAW,SACb,QAAQ,OAAO,CAAC,MAAM,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,YAAY,EAAE,SAAS,MAAM,CAAC,IAC7F;AACJ,aAAO,OAAO,QAAQ,SAAS,MAAM,GAAG,MAAM,KAAK,IAAI;AAAA,IACzD;AAAA,IAEA,MAAM,QAAQ,QAAsB,SAAqB,gBAAyD;AAChH,YAAM,UAAU,WAAW,MAAM;AACjC,YAAM,UAAU,gBAAgB,aAAa;AAC7C,UAAI,gBAAgB,WAAY,OAAM,kBAAkB,QAAQ,SAAS,OAAO;AAChF,aAAO,eAAe,QAAQ,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAChE;AAAA,IAEA,MAAM,MAAM,QAAqC;AAC/C,YAAM,UAAU,SAAS,IAAI,OAAO,MAAM;AAC1C,UAAI,CAAC,QAAS;AACd,eAAS,OAAO,OAAO,MAAM;AAC7B,oBAAc,QAAQ,MAAM;AAAA,IAC9B;AAAA,IAEA,QAAQ,SAA0C;AAChD,oBAAc,IAAI,OAAO;AACzB,aAAO;AAAA,QACL,QAAQ;AACN,wBAAc,OAAO,OAAO;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAAA,IAEA,UAAgB;AACd,eAAS,MAAM;AACf,gBAAU;AACV,iBAAW,SAAS,QAAQ,OAAO,GAAG;AACpC,qBAAa,MAAM,KAAK;AACxB,cAAM,OAAO,IAAI,MAAM,oBAAoB,CAAC;AAAA,MAC9C;AACA,cAAQ,MAAM;AACd,eAAS,MAAM;AACf,oBAAc,MAAM;AACpB,oBAAc,MAAM;AACpB,yBAAmB;AAAA,IACrB;AAAA,EACF;AACF;","names":["encrypt"]}
|
|
@@ -184,6 +184,8 @@ interface CvmTransport {
|
|
|
184
184
|
onEvent(handler: (server: CvmServerRef, message: McpMessage) => void): {
|
|
185
185
|
close(): void;
|
|
186
186
|
};
|
|
187
|
+
/** Release all transport resources when the owning service is disposed. */
|
|
188
|
+
dispose?(): void;
|
|
187
189
|
}
|
|
188
190
|
/** Options for {@link createCvmService}. */
|
|
189
191
|
interface CvmServiceOptions {
|