@openclaw/nostr 2026.5.2 → 2026.5.3-beta.2

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.
Files changed (59) hide show
  1. package/dist/api.js +532 -0
  2. package/dist/channel-DfEqBtUh.js +1466 -0
  3. package/dist/channel-plugin-api.js +2 -0
  4. package/dist/config-schema-DIk4jlBg.js +64 -0
  5. package/dist/default-relays-DLwdWOTu.js +4 -0
  6. package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
  7. package/dist/index.js +84 -0
  8. package/dist/runtime-api.js +2 -0
  9. package/dist/setup-api.js +2 -0
  10. package/dist/setup-entry.js +11 -0
  11. package/dist/setup-plugin-api.js +165 -0
  12. package/dist/setup-surface-DxAaUTyC.js +336 -0
  13. package/dist/test-api.js +2 -0
  14. package/package.json +15 -6
  15. package/api.ts +0 -10
  16. package/channel-plugin-api.ts +0 -1
  17. package/index.ts +0 -97
  18. package/runtime-api.ts +0 -6
  19. package/setup-api.ts +0 -1
  20. package/setup-entry.ts +0 -9
  21. package/setup-plugin-api.ts +0 -3
  22. package/src/channel-api.ts +0 -15
  23. package/src/channel.inbound.test.ts +0 -176
  24. package/src/channel.outbound.test.ts +0 -128
  25. package/src/channel.setup.ts +0 -231
  26. package/src/channel.test.ts +0 -519
  27. package/src/channel.ts +0 -207
  28. package/src/config-schema.ts +0 -98
  29. package/src/default-relays.ts +0 -1
  30. package/src/gateway.ts +0 -302
  31. package/src/inbound-direct-dm-runtime.ts +0 -1
  32. package/src/metrics.ts +0 -458
  33. package/src/nostr-bus.fuzz.test.ts +0 -360
  34. package/src/nostr-bus.inbound.test.ts +0 -526
  35. package/src/nostr-bus.integration.test.ts +0 -472
  36. package/src/nostr-bus.test.ts +0 -190
  37. package/src/nostr-bus.ts +0 -789
  38. package/src/nostr-key-utils.ts +0 -94
  39. package/src/nostr-profile-core.ts +0 -134
  40. package/src/nostr-profile-http-runtime.ts +0 -6
  41. package/src/nostr-profile-http.test.ts +0 -632
  42. package/src/nostr-profile-http.ts +0 -594
  43. package/src/nostr-profile-import.test.ts +0 -119
  44. package/src/nostr-profile-import.ts +0 -262
  45. package/src/nostr-profile-url-safety.ts +0 -21
  46. package/src/nostr-profile.fuzz.test.ts +0 -430
  47. package/src/nostr-profile.test.ts +0 -412
  48. package/src/nostr-profile.ts +0 -144
  49. package/src/nostr-state-store.test.ts +0 -237
  50. package/src/nostr-state-store.ts +0 -223
  51. package/src/runtime.ts +0 -9
  52. package/src/seen-tracker.ts +0 -289
  53. package/src/session-route.ts +0 -25
  54. package/src/setup-surface.ts +0 -265
  55. package/src/test-fixtures.ts +0 -45
  56. package/src/types.ts +0 -117
  57. package/test/setup.ts +0 -5
  58. package/test-api.ts +0 -1
  59. package/tsconfig.json +0 -16
package/src/channel.ts DELETED
@@ -1,207 +0,0 @@
1
- import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
2
- import {
3
- createScopedDmSecurityResolver,
4
- createTopLevelChannelConfigAdapter,
5
- } from "openclaw/plugin-sdk/channel-config-helpers";
6
- import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
7
- import {
8
- buildPassiveChannelStatusSummary,
9
- buildTrafficStatusSummary,
10
- } from "openclaw/plugin-sdk/extension-shared";
11
- import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-helpers";
12
- import {
13
- buildChannelConfigSchema,
14
- collectStatusIssuesFromLastError,
15
- createDefaultChannelRuntimeState,
16
- DEFAULT_ACCOUNT_ID,
17
- formatPairingApproveHint,
18
- type ChannelPlugin,
19
- } from "./channel-api.js";
20
- import type { NostrProfile } from "./config-schema.js";
21
- import { NostrConfigSchema } from "./config-schema.js";
22
- import {
23
- getActiveNostrBuses,
24
- nostrOutboundAdapter,
25
- nostrPairingTextAdapter,
26
- startNostrGatewayAccount,
27
- } from "./gateway.js";
28
- import { normalizePubkey } from "./nostr-key-utils.js";
29
- import type { ProfilePublishResult } from "./nostr-profile.js";
30
- import { resolveNostrOutboundSessionRoute } from "./session-route.js";
31
- import { nostrSetupAdapter, nostrSetupWizard } from "./setup-surface.js";
32
- import {
33
- listNostrAccountIds,
34
- resolveDefaultNostrAccountId,
35
- resolveNostrAccount,
36
- type ResolvedNostrAccount,
37
- } from "./types.js";
38
-
39
- const resolveNostrDmPolicy = createScopedDmSecurityResolver<ResolvedNostrAccount>({
40
- channelKey: "nostr",
41
- resolvePolicy: (account) => account.config.dmPolicy,
42
- resolveAllowFrom: (account) => account.config.allowFrom,
43
- policyPathSuffix: "dmPolicy",
44
- defaultPolicy: "pairing",
45
- approveHint: formatPairingApproveHint("nostr"),
46
- normalizeEntry: (raw) => {
47
- try {
48
- return normalizePubkey(raw.trim().replace(/^nostr:/i, ""));
49
- } catch {
50
- return raw.trim();
51
- }
52
- },
53
- });
54
-
55
- const nostrConfigAdapter = createTopLevelChannelConfigAdapter<ResolvedNostrAccount>({
56
- sectionKey: "nostr",
57
- resolveAccount: (cfg) => resolveNostrAccount({ cfg }),
58
- listAccountIds: listNostrAccountIds,
59
- defaultAccountId: resolveDefaultNostrAccountId,
60
- deleteMode: "clear-fields",
61
- clearBaseFields: [
62
- "name",
63
- "defaultAccount",
64
- "privateKey",
65
- "relays",
66
- "dmPolicy",
67
- "allowFrom",
68
- "profile",
69
- ],
70
- resolveAllowFrom: (account) => account.config.allowFrom,
71
- formatAllowFrom: (allowFrom) =>
72
- allowFrom
73
- .map((entry) => String(entry).trim())
74
- .filter(Boolean)
75
- .map((entry) => {
76
- if (entry === "*") {
77
- return "*";
78
- }
79
- try {
80
- return normalizePubkey(entry);
81
- } catch {
82
- return entry;
83
- }
84
- })
85
- .filter(Boolean),
86
- });
87
-
88
- export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = createChatChannelPlugin({
89
- base: {
90
- id: "nostr",
91
- meta: {
92
- id: "nostr",
93
- label: "Nostr",
94
- selectionLabel: "Nostr",
95
- docsPath: "/channels/nostr",
96
- docsLabel: "nostr",
97
- blurb: "Decentralized DMs via Nostr relays (NIP-04)",
98
- order: 100,
99
- },
100
- capabilities: {
101
- chatTypes: ["direct"], // DMs only for MVP
102
- media: false, // No media for MVP
103
- },
104
- reload: { configPrefixes: ["channels.nostr"] },
105
- configSchema: buildChannelConfigSchema(NostrConfigSchema),
106
- setup: nostrSetupAdapter,
107
- setupWizard: nostrSetupWizard,
108
- config: {
109
- ...nostrConfigAdapter,
110
- isConfigured: (account) => account.configured,
111
- describeAccount: (account) =>
112
- describeAccountSnapshot({
113
- account,
114
- configured: account.configured,
115
- extra: {
116
- publicKey: account.publicKey,
117
- },
118
- }),
119
- },
120
- messaging: {
121
- targetPrefixes: ["nostr"],
122
- normalizeTarget: (target) => {
123
- // Strip nostr: prefix if present
124
- const cleaned = target.trim().replace(/^nostr:/i, "");
125
- try {
126
- return normalizePubkey(cleaned);
127
- } catch {
128
- return cleaned;
129
- }
130
- },
131
- targetResolver: {
132
- looksLikeId: (input) => {
133
- const trimmed = input.trim();
134
- return trimmed.startsWith("npub1") || /^[0-9a-fA-F]{64}$/.test(trimmed);
135
- },
136
- hint: "<npub|hex pubkey|nostr:npub...>",
137
- },
138
- resolveOutboundSessionRoute: (params) => resolveNostrOutboundSessionRoute(params),
139
- },
140
- status: {
141
- ...createComputedAccountStatusAdapter<ResolvedNostrAccount>({
142
- defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
143
- collectStatusIssues: (accounts) => collectStatusIssuesFromLastError("nostr", accounts),
144
- buildChannelSummary: ({ snapshot }) =>
145
- buildPassiveChannelStatusSummary(snapshot, {
146
- publicKey: snapshot.publicKey ?? null,
147
- }),
148
- resolveAccountSnapshot: ({ account, runtime }) => ({
149
- accountId: account.accountId,
150
- name: account.name,
151
- enabled: account.enabled,
152
- configured: account.configured,
153
- extra: {
154
- publicKey: account.publicKey,
155
- profile: account.profile,
156
- ...buildTrafficStatusSummary(runtime),
157
- },
158
- }),
159
- }),
160
- },
161
- gateway: {
162
- startAccount: startNostrGatewayAccount,
163
- },
164
- },
165
- pairing: {
166
- text: nostrPairingTextAdapter,
167
- },
168
- security: {
169
- resolveDmPolicy: resolveNostrDmPolicy,
170
- },
171
- outbound: nostrOutboundAdapter,
172
- });
173
-
174
- /**
175
- * Publish a profile (kind:0) for a Nostr account.
176
- * @param accountId - Account ID (defaults to "default")
177
- * @param profile - Profile data to publish
178
- * @returns Publish results with successes and failures
179
- * @throws Error if account is not running
180
- */
181
- export async function publishNostrProfile(
182
- accountId: string = DEFAULT_ACCOUNT_ID,
183
- profile: NostrProfile,
184
- ): Promise<ProfilePublishResult> {
185
- const bus = getActiveNostrBuses().get(accountId);
186
- if (!bus) {
187
- throw new Error(`Nostr bus not running for account ${accountId}`);
188
- }
189
- return bus.publishProfile(profile);
190
- }
191
-
192
- /**
193
- * Get profile publish state for a Nostr account.
194
- * @param accountId - Account ID (defaults to "default")
195
- * @returns Profile publish state or null if account not running
196
- */
197
- export async function getNostrProfileState(accountId: string = DEFAULT_ACCOUNT_ID): Promise<{
198
- lastPublishedAt: number | null;
199
- lastPublishedEventId: string | null;
200
- lastPublishResults: Record<string, "ok" | "failed" | "timeout"> | null;
201
- } | null> {
202
- const bus = getActiveNostrBuses().get(accountId);
203
- if (!bus) {
204
- return null;
205
- }
206
- return bus.getProfileState();
207
- }
@@ -1,98 +0,0 @@
1
- import {
2
- AllowFromListSchema,
3
- DmPolicySchema,
4
- MarkdownConfigSchema,
5
- } from "openclaw/plugin-sdk/channel-config-primitives";
6
- import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
7
- import { z } from "openclaw/plugin-sdk/zod";
8
-
9
- /**
10
- * Validates https:// URLs only (no javascript:, data:, file:, etc.)
11
- */
12
- const safeUrlSchema = z
13
- .string()
14
- .url()
15
- .refine(
16
- (url) => {
17
- try {
18
- const parsed = new URL(url);
19
- return parsed.protocol === "https:";
20
- } catch {
21
- return false;
22
- }
23
- },
24
- { message: "URL must use https:// protocol" },
25
- );
26
-
27
- /**
28
- * NIP-01 profile metadata schema
29
- * https://github.com/nostr-protocol/nips/blob/master/01.md
30
- */
31
- export const NostrProfileSchema = z.object({
32
- /** Username (NIP-01: name) - max 256 chars */
33
- name: z.string().max(256).optional(),
34
-
35
- /** Display name (NIP-01: display_name) - max 256 chars */
36
- displayName: z.string().max(256).optional(),
37
-
38
- /** Bio/description (NIP-01: about) - max 2000 chars */
39
- about: z.string().max(2000).optional(),
40
-
41
- /** Profile picture URL (must be https) */
42
- picture: safeUrlSchema.optional(),
43
-
44
- /** Banner image URL (must be https) */
45
- banner: safeUrlSchema.optional(),
46
-
47
- /** Website URL (must be https) */
48
- website: safeUrlSchema.optional(),
49
-
50
- /** NIP-05 identifier (e.g., "user@example.com") */
51
- nip05: z.string().optional(),
52
-
53
- /** Lightning address (LUD-16) */
54
- lud16: z.string().optional(),
55
- });
56
-
57
- export interface NostrProfile {
58
- name?: string;
59
- displayName?: string;
60
- about?: string;
61
- picture?: string;
62
- banner?: string;
63
- website?: string;
64
- nip05?: string;
65
- lud16?: string;
66
- }
67
-
68
- /**
69
- * Zod schema for channels.nostr.* configuration
70
- */
71
- export const NostrConfigSchema = z.object({
72
- /** Account name (optional display name) */
73
- name: z.string().optional(),
74
-
75
- /** Optional default account id for routing/account selection. */
76
- defaultAccount: z.string().optional(),
77
-
78
- /** Whether this channel is enabled */
79
- enabled: z.boolean().optional(),
80
-
81
- /** Markdown formatting overrides (tables). */
82
- markdown: MarkdownConfigSchema,
83
-
84
- /** Private key in hex or nsec bech32 format */
85
- privateKey: buildSecretInputSchema().optional(),
86
-
87
- /** WebSocket relay URLs to connect to */
88
- relays: z.array(z.string()).optional(),
89
-
90
- /** DM access policy: pairing, allowlist, open, or disabled */
91
- dmPolicy: DmPolicySchema.optional(),
92
-
93
- /** Allowed sender pubkeys (npub or hex format) */
94
- allowFrom: AllowFromListSchema,
95
-
96
- /** Profile metadata (NIP-01 kind:0 content) */
97
- profile: NostrProfileSchema.optional(),
98
- });
@@ -1 +0,0 @@
1
- export const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
package/src/gateway.ts DELETED
@@ -1,302 +0,0 @@
1
- import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
2
- import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
3
- import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
4
- import {
5
- createPreCryptoDirectDmAuthorizer,
6
- type ChannelOutboundAdapter,
7
- resolveInboundDirectDmAccessWithRuntime,
8
- type ChannelPlugin,
9
- } from "./channel-api.js";
10
- import type { MetricEvent, MetricsSnapshot } from "./metrics.js";
11
- import { startNostrBus, type NostrBusHandle } from "./nostr-bus.js";
12
- import { normalizePubkey } from "./nostr-key-utils.js";
13
- import { getNostrRuntime } from "./runtime.js";
14
- import { resolveDefaultNostrAccountId, type ResolvedNostrAccount } from "./types.js";
15
-
16
- type NostrGatewayStart = NonNullable<
17
- NonNullable<ChannelPlugin<ResolvedNostrAccount>["gateway"]>["startAccount"]
18
- >;
19
- type NostrOutboundAdapter = Pick<
20
- ChannelOutboundAdapter,
21
- "deliveryMode" | "textChunkLimit" | "sendText"
22
- > & {
23
- sendText: NonNullable<ChannelOutboundAdapter["sendText"]>;
24
- };
25
-
26
- const activeBuses = new Map<string, NostrBusHandle>();
27
- const metricsSnapshots = new Map<string, MetricsSnapshot>();
28
-
29
- function normalizeNostrAllowEntry(entry: string): string | null {
30
- const trimmed = entry.trim();
31
- if (!trimmed) {
32
- return null;
33
- }
34
- if (trimmed === "*") {
35
- return "*";
36
- }
37
- try {
38
- return normalizePubkey(trimmed.replace(/^nostr:/i, ""));
39
- } catch {
40
- return null;
41
- }
42
- }
43
-
44
- function isNostrSenderAllowed(senderPubkey: string, allowFrom: string[]): boolean {
45
- const normalizedSender = normalizePubkey(senderPubkey);
46
- for (const entry of allowFrom) {
47
- const normalized = normalizeNostrAllowEntry(entry);
48
- if (normalized === "*" || normalized === normalizedSender) {
49
- return true;
50
- }
51
- }
52
- return false;
53
- }
54
-
55
- async function resolveNostrDirectAccess(params: {
56
- cfg: OpenClawConfig;
57
- accountId: string;
58
- dmPolicy: "pairing" | "allowlist" | "open" | "disabled";
59
- allowFrom: Array<string | number> | undefined;
60
- senderPubkey: string;
61
- rawBody: string;
62
- runtime: Parameters<typeof resolveInboundDirectDmAccessWithRuntime>[0]["runtime"];
63
- }) {
64
- return resolveInboundDirectDmAccessWithRuntime({
65
- cfg: params.cfg,
66
- channel: "nostr",
67
- accountId: params.accountId,
68
- dmPolicy: params.dmPolicy,
69
- allowFrom: params.allowFrom,
70
- senderId: params.senderPubkey,
71
- rawBody: params.rawBody,
72
- isSenderAllowed: isNostrSenderAllowed,
73
- runtime: params.runtime,
74
- modeWhenAccessGroupsOff: "configured",
75
- });
76
- }
77
-
78
- export const startNostrGatewayAccount: NostrGatewayStart = async (ctx) => {
79
- const account = ctx.account;
80
- ctx.setStatus({
81
- accountId: account.accountId,
82
- publicKey: account.publicKey,
83
- });
84
- ctx.log?.info?.(`[${account.accountId}] starting Nostr provider (pubkey: ${account.publicKey})`);
85
-
86
- if (!account.configured) {
87
- throw new Error("Nostr private key not configured");
88
- }
89
-
90
- const runtime = getNostrRuntime();
91
- const pairing = createChannelPairingController({
92
- core: runtime,
93
- channel: "nostr",
94
- accountId: account.accountId,
95
- });
96
- const resolveInboundAccess = async (senderPubkey: string, rawBody: string) =>
97
- await resolveNostrDirectAccess({
98
- cfg: ctx.cfg,
99
- accountId: account.accountId,
100
- dmPolicy: account.config.dmPolicy ?? "pairing",
101
- allowFrom: account.config.allowFrom,
102
- senderPubkey,
103
- rawBody,
104
- runtime: {
105
- shouldComputeCommandAuthorized: runtime.channel.commands.shouldComputeCommandAuthorized,
106
- resolveCommandAuthorizedFromAuthorizers:
107
- runtime.channel.commands.resolveCommandAuthorizedFromAuthorizers,
108
- },
109
- });
110
-
111
- let busHandle: NostrBusHandle | null = null;
112
-
113
- const authorizeSender = createPreCryptoDirectDmAuthorizer({
114
- resolveAccess: async (senderPubkey) => await resolveInboundAccess(senderPubkey, ""),
115
- issuePairingChallenge: async ({ senderId, reply }) => {
116
- await pairing.issueChallenge({
117
- senderId,
118
- senderIdLine: `Your Nostr pubkey: ${senderId}`,
119
- sendPairingReply: reply,
120
- onCreated: () => {
121
- ctx.log?.debug?.(`[${account.accountId}] nostr pairing request sender=${senderId}`);
122
- },
123
- onReplyError: (err) => {
124
- ctx.log?.warn?.(
125
- `[${account.accountId}] nostr pairing reply failed for ${senderId}: ${String(err)}`,
126
- );
127
- },
128
- });
129
- },
130
- onBlocked: ({ senderId, reason }) => {
131
- ctx.log?.debug?.(`[${account.accountId}] blocked Nostr sender ${senderId} (${reason})`);
132
- },
133
- });
134
-
135
- const bus = await startNostrBus({
136
- accountId: account.accountId,
137
- privateKey: account.privateKey,
138
- relays: account.relays,
139
- authorizeSender: async ({ senderPubkey, reply }) =>
140
- await authorizeSender({ senderId: senderPubkey, reply }),
141
- onMessage: async (senderPubkey, text, reply, meta) => {
142
- const resolvedAccess = await resolveInboundAccess(senderPubkey, text);
143
- if (resolvedAccess.access.decision !== "allow") {
144
- ctx.log?.warn?.(
145
- `[${account.accountId}] dropping Nostr DM after preflight drift (${senderPubkey}, ${resolvedAccess.access.reason})`,
146
- );
147
- return;
148
- }
149
-
150
- const { dispatchInboundDirectDmWithRuntime } = await import("./inbound-direct-dm-runtime.js");
151
- await dispatchInboundDirectDmWithRuntime({
152
- cfg: ctx.cfg,
153
- runtime,
154
- channel: "nostr",
155
- channelLabel: "Nostr",
156
- accountId: account.accountId,
157
- peer: {
158
- kind: "direct",
159
- id: senderPubkey,
160
- },
161
- senderId: senderPubkey,
162
- senderAddress: `nostr:${senderPubkey}`,
163
- recipientAddress: `nostr:${account.publicKey}`,
164
- conversationLabel: senderPubkey,
165
- rawBody: text,
166
- messageId: meta.eventId,
167
- timestamp: meta.createdAt * 1000,
168
- commandAuthorized: resolvedAccess.commandAuthorized,
169
- deliver: async (payload) => {
170
- const outboundText =
171
- payload && typeof payload === "object" && "text" in payload
172
- ? ((payload as { text?: string }).text ?? "")
173
- : "";
174
- if (!outboundText.trim()) {
175
- return;
176
- }
177
- const tableMode = runtime.channel.text.resolveMarkdownTableMode({
178
- cfg: ctx.cfg,
179
- channel: "nostr",
180
- accountId: account.accountId,
181
- });
182
- await reply(runtime.channel.text.convertMarkdownTables(outboundText, tableMode));
183
- },
184
- onRecordError: (err) => {
185
- ctx.log?.error?.(
186
- `[${account.accountId}] failed recording Nostr inbound session: ${String(err)}`,
187
- );
188
- },
189
- onDispatchError: (err, info) => {
190
- ctx.log?.error?.(
191
- `[${account.accountId}] Nostr ${info.kind} reply failed: ${String(err)}`,
192
- );
193
- },
194
- });
195
- },
196
- onError: (error, context) => {
197
- ctx.log?.error?.(`[${account.accountId}] Nostr error (${context}): ${error.message}`);
198
- },
199
- onConnect: (relay) => {
200
- ctx.log?.debug?.(`[${account.accountId}] Connected to relay: ${relay}`);
201
- },
202
- onDisconnect: (relay) => {
203
- ctx.log?.debug?.(`[${account.accountId}] Disconnected from relay: ${relay}`);
204
- },
205
- onEose: (relays) => {
206
- ctx.log?.debug?.(`[${account.accountId}] EOSE received from relays: ${relays}`);
207
- },
208
- onMetric: (event: MetricEvent) => {
209
- if (event.name.startsWith("event.rejected.")) {
210
- ctx.log?.debug?.(
211
- `[${account.accountId}] Metric: ${event.name} ${JSON.stringify(event.labels)}`,
212
- );
213
- } else if (event.name === "relay.circuit_breaker.open") {
214
- ctx.log?.warn?.(
215
- `[${account.accountId}] Circuit breaker opened for relay: ${event.labels?.relay}`,
216
- );
217
- } else if (event.name === "relay.circuit_breaker.close") {
218
- ctx.log?.info?.(
219
- `[${account.accountId}] Circuit breaker closed for relay: ${event.labels?.relay}`,
220
- );
221
- } else if (event.name === "relay.error") {
222
- ctx.log?.debug?.(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
223
- }
224
- if (busHandle) {
225
- metricsSnapshots.set(account.accountId, busHandle.getMetrics());
226
- }
227
- },
228
- });
229
-
230
- busHandle = bus;
231
- activeBuses.set(account.accountId, bus);
232
-
233
- ctx.log?.info?.(
234
- `[${account.accountId}] Nostr provider started, connected to ${account.relays.length} relay(s)`,
235
- );
236
-
237
- return {
238
- stop: () => {
239
- bus.close();
240
- activeBuses.delete(account.accountId);
241
- metricsSnapshots.delete(account.accountId);
242
- ctx.log?.info?.(`[${account.accountId}] Nostr provider stopped`);
243
- },
244
- };
245
- };
246
-
247
- export const nostrPairingTextAdapter = {
248
- idLabel: "nostrPubkey",
249
- message: "Your pairing request has been approved!",
250
- normalizeAllowEntry: (entry: string) => {
251
- try {
252
- return normalizePubkey(entry.trim().replace(/^nostr:/i, ""));
253
- } catch {
254
- return entry.trim();
255
- }
256
- },
257
- notify: async ({
258
- cfg,
259
- id,
260
- message,
261
- accountId,
262
- }: {
263
- cfg: OpenClawConfig;
264
- id: string;
265
- message: string;
266
- accountId?: string;
267
- }) => {
268
- const bus = activeBuses.get(accountId ?? resolveDefaultNostrAccountId(cfg));
269
- if (bus) {
270
- await bus.sendDm(id, message);
271
- }
272
- },
273
- };
274
-
275
- export const nostrOutboundAdapter: NostrOutboundAdapter = {
276
- deliveryMode: "direct",
277
- textChunkLimit: 4000,
278
- sendText: async ({ cfg, to, text, accountId }) => {
279
- const core = getNostrRuntime();
280
- const aid = accountId ?? resolveDefaultNostrAccountId(cfg);
281
- const bus = activeBuses.get(aid);
282
- if (!bus) {
283
- throw new Error(`Nostr bus not running for account ${aid}`);
284
- }
285
- const tableMode = core.channel.text.resolveMarkdownTableMode({
286
- cfg,
287
- channel: "nostr",
288
- accountId: aid,
289
- });
290
- const message = core.channel.text.convertMarkdownTables(text ?? "", tableMode);
291
- const normalizedTo = normalizePubkey(to);
292
- await bus.sendDm(normalizedTo, message);
293
- return attachChannelToResult("nostr", {
294
- to: normalizedTo,
295
- messageId: `nostr-${Date.now()}`,
296
- });
297
- },
298
- };
299
-
300
- export function getActiveNostrBuses(): Map<string, NostrBusHandle> {
301
- return new Map(activeBuses);
302
- }
@@ -1 +0,0 @@
1
- export { dispatchInboundDirectDmWithRuntime } from "openclaw/plugin-sdk/direct-dm";