@openclaw/nostr 2026.5.2 → 2026.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +532 -0
- package/dist/channel-DfEqBtUh.js +1466 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/config-schema-DIk4jlBg.js +64 -0
- package/dist/default-relays-DLwdWOTu.js +4 -0
- package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
- package/dist/index.js +84 -0
- package/dist/runtime-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.js +165 -0
- package/dist/setup-surface-DxAaUTyC.js +336 -0
- package/dist/test-api.js +2 -0
- package/package.json +15 -6
- package/api.ts +0 -10
- package/channel-plugin-api.ts +0 -1
- package/index.ts +0 -97
- package/runtime-api.ts +0 -6
- package/setup-api.ts +0 -1
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -3
- package/src/channel-api.ts +0 -15
- package/src/channel.inbound.test.ts +0 -176
- package/src/channel.outbound.test.ts +0 -128
- package/src/channel.setup.ts +0 -231
- package/src/channel.test.ts +0 -519
- package/src/channel.ts +0 -207
- package/src/config-schema.ts +0 -98
- package/src/default-relays.ts +0 -1
- package/src/gateway.ts +0 -302
- package/src/inbound-direct-dm-runtime.ts +0 -1
- package/src/metrics.ts +0 -458
- package/src/nostr-bus.fuzz.test.ts +0 -360
- package/src/nostr-bus.inbound.test.ts +0 -526
- package/src/nostr-bus.integration.test.ts +0 -472
- package/src/nostr-bus.test.ts +0 -190
- package/src/nostr-bus.ts +0 -789
- package/src/nostr-key-utils.ts +0 -94
- package/src/nostr-profile-core.ts +0 -134
- package/src/nostr-profile-http-runtime.ts +0 -6
- package/src/nostr-profile-http.test.ts +0 -632
- package/src/nostr-profile-http.ts +0 -594
- package/src/nostr-profile-import.test.ts +0 -119
- package/src/nostr-profile-import.ts +0 -262
- package/src/nostr-profile-url-safety.ts +0 -21
- package/src/nostr-profile.fuzz.test.ts +0 -430
- package/src/nostr-profile.test.ts +0 -412
- package/src/nostr-profile.ts +0 -144
- package/src/nostr-state-store.test.ts +0 -237
- package/src/nostr-state-store.ts +0 -223
- package/src/runtime.ts +0 -9
- package/src/seen-tracker.ts +0 -289
- package/src/session-route.ts +0 -25
- package/src/setup-surface.ts +0 -265
- package/src/test-fixtures.ts +0 -45
- package/src/types.ts +0 -117
- package/test/setup.ts +0 -5
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/nostr",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.3-beta.1",
|
|
4
4
|
"description": "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"openclaw": "workspace:*"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"openclaw": ">=2026.5.
|
|
19
|
+
"openclaw": ">=2026.5.3-beta.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependenciesMeta": {
|
|
22
22
|
"openclaw": {
|
|
@@ -54,14 +54,23 @@
|
|
|
54
54
|
"minHostVersion": ">=2026.4.10"
|
|
55
55
|
},
|
|
56
56
|
"compat": {
|
|
57
|
-
"pluginApi": ">=2026.5.
|
|
57
|
+
"pluginApi": ">=2026.5.3-beta.1"
|
|
58
58
|
},
|
|
59
59
|
"build": {
|
|
60
|
-
"openclawVersion": "2026.5.
|
|
60
|
+
"openclawVersion": "2026.5.3-beta.1"
|
|
61
61
|
},
|
|
62
62
|
"release": {
|
|
63
63
|
"publishToClawHub": true,
|
|
64
64
|
"publishToNpm": true
|
|
65
|
-
}
|
|
66
|
-
|
|
65
|
+
},
|
|
66
|
+
"runtimeExtensions": [
|
|
67
|
+
"./dist/index.js"
|
|
68
|
+
],
|
|
69
|
+
"runtimeSetupEntry": "./dist/setup-entry.js"
|
|
70
|
+
},
|
|
71
|
+
"files": [
|
|
72
|
+
"dist/**",
|
|
73
|
+
"openclaw.plugin.json",
|
|
74
|
+
"README.md"
|
|
75
|
+
]
|
|
67
76
|
}
|
package/api.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
getPluginRuntimeGatewayRequestScope,
|
|
3
|
-
type OpenClawConfig,
|
|
4
|
-
type PluginRuntime,
|
|
5
|
-
} from "./runtime-api.js";
|
|
6
|
-
export { nostrPlugin } from "./src/channel.js";
|
|
7
|
-
export { createNostrProfileHttpHandler } from "./src/nostr-profile-http.js";
|
|
8
|
-
export { getNostrRuntime, setNostrRuntime } from "./src/runtime.js";
|
|
9
|
-
export { resolveNostrAccount } from "./src/types.js";
|
|
10
|
-
export type { ResolvedNostrAccount } from "./src/types.js";
|
package/channel-plugin-api.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { nostrPlugin } from "./src/channel.js";
|
package/index.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineBundledChannelEntry,
|
|
3
|
-
loadBundledEntryExportSync,
|
|
4
|
-
} from "openclaw/plugin-sdk/channel-entry-contract";
|
|
5
|
-
import type { OpenClawConfig, PluginRuntime, ResolvedNostrAccount } from "./api.js";
|
|
6
|
-
|
|
7
|
-
function createNostrProfileHttpHandler() {
|
|
8
|
-
return loadBundledEntryExportSync<
|
|
9
|
-
(params: Record<string, unknown>) => (ctx: unknown) => Promise<void> | void
|
|
10
|
-
>(import.meta.url, {
|
|
11
|
-
specifier: "./api.js",
|
|
12
|
-
exportName: "createNostrProfileHttpHandler",
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function getNostrRuntime() {
|
|
17
|
-
return loadBundledEntryExportSync<() => PluginRuntime>(import.meta.url, {
|
|
18
|
-
specifier: "./api.js",
|
|
19
|
-
exportName: "getNostrRuntime",
|
|
20
|
-
})();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function resolveNostrAccount(params: { cfg: unknown; accountId: string }) {
|
|
24
|
-
return loadBundledEntryExportSync<
|
|
25
|
-
(params: { cfg: unknown; accountId: string }) => ResolvedNostrAccount
|
|
26
|
-
>(import.meta.url, {
|
|
27
|
-
specifier: "./api.js",
|
|
28
|
-
exportName: "resolveNostrAccount",
|
|
29
|
-
})(params);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default defineBundledChannelEntry({
|
|
33
|
-
id: "nostr",
|
|
34
|
-
name: "Nostr",
|
|
35
|
-
description: "Nostr DM channel plugin via NIP-04",
|
|
36
|
-
importMetaUrl: import.meta.url,
|
|
37
|
-
plugin: {
|
|
38
|
-
specifier: "./channel-plugin-api.js",
|
|
39
|
-
exportName: "nostrPlugin",
|
|
40
|
-
},
|
|
41
|
-
runtime: {
|
|
42
|
-
specifier: "./api.js",
|
|
43
|
-
exportName: "setNostrRuntime",
|
|
44
|
-
},
|
|
45
|
-
registerFull(api) {
|
|
46
|
-
const httpHandler = createNostrProfileHttpHandler()({
|
|
47
|
-
getConfigProfile: (accountId: string) => {
|
|
48
|
-
const runtime = getNostrRuntime();
|
|
49
|
-
const cfg = runtime.config.current() as OpenClawConfig;
|
|
50
|
-
const account = resolveNostrAccount({ cfg, accountId });
|
|
51
|
-
return account.profile;
|
|
52
|
-
},
|
|
53
|
-
updateConfigProfile: async (accountId: string, profile: unknown) => {
|
|
54
|
-
const runtime = getNostrRuntime();
|
|
55
|
-
const cfg = runtime.config.current() as OpenClawConfig;
|
|
56
|
-
|
|
57
|
-
const channels = (cfg.channels ?? {}) as Record<string, unknown>;
|
|
58
|
-
const nostrConfig = (channels.nostr ?? {}) as Record<string, unknown>;
|
|
59
|
-
|
|
60
|
-
await runtime.config.replaceConfigFile({
|
|
61
|
-
nextConfig: {
|
|
62
|
-
...cfg,
|
|
63
|
-
channels: {
|
|
64
|
-
...channels,
|
|
65
|
-
nostr: {
|
|
66
|
-
...nostrConfig,
|
|
67
|
-
profile,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
afterWrite: { mode: "auto" },
|
|
72
|
-
});
|
|
73
|
-
},
|
|
74
|
-
getAccountInfo: (accountId: string) => {
|
|
75
|
-
const runtime = getNostrRuntime();
|
|
76
|
-
const cfg = runtime.config.current() as OpenClawConfig;
|
|
77
|
-
const account = resolveNostrAccount({ cfg, accountId });
|
|
78
|
-
if (!account.configured || !account.publicKey) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
pubkey: account.publicKey,
|
|
83
|
-
relays: account.relays,
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
log: api.logger,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
api.registerHttpRoute({
|
|
90
|
-
path: "/api/channels/nostr",
|
|
91
|
-
auth: "gateway",
|
|
92
|
-
match: "prefix",
|
|
93
|
-
gatewayRuntimeScopeSurface: "trusted-operator",
|
|
94
|
-
handler: httpHandler,
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
});
|
package/runtime-api.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// Private runtime barrel for the bundled Nostr extension.
|
|
2
|
-
// Keep this barrel thin and aligned with the local extension surface.
|
|
3
|
-
|
|
4
|
-
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
5
|
-
export { getPluginRuntimeGatewayRequestScope } from "openclaw/plugin-sdk/plugin-runtime";
|
|
6
|
-
export type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
|
package/setup-api.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { nostrSetupAdapter, nostrSetupWizard } from "./src/setup-surface.js";
|
package/setup-entry.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
|
2
|
-
|
|
3
|
-
export default defineBundledChannelSetupEntry({
|
|
4
|
-
importMetaUrl: import.meta.url,
|
|
5
|
-
plugin: {
|
|
6
|
-
specifier: "./setup-plugin-api.js",
|
|
7
|
-
exportName: "nostrSetupPlugin",
|
|
8
|
-
},
|
|
9
|
-
});
|
package/setup-plugin-api.ts
DELETED
package/src/channel-api.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
buildChannelConfigSchema,
|
|
3
|
-
DEFAULT_ACCOUNT_ID,
|
|
4
|
-
formatPairingApproveHint,
|
|
5
|
-
type ChannelPlugin,
|
|
6
|
-
} from "openclaw/plugin-sdk/channel-plugin-common";
|
|
7
|
-
export type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-contract";
|
|
8
|
-
export {
|
|
9
|
-
collectStatusIssuesFromLastError,
|
|
10
|
-
createDefaultChannelRuntimeState,
|
|
11
|
-
} from "openclaw/plugin-sdk/status-helpers";
|
|
12
|
-
export {
|
|
13
|
-
createPreCryptoDirectDmAuthorizer,
|
|
14
|
-
resolveInboundDirectDmAccessWithRuntime,
|
|
15
|
-
} from "openclaw/plugin-sdk/direct-dm-access";
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-helpers";
|
|
2
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import type { PluginRuntime } from "../runtime-api.js";
|
|
4
|
-
import { startNostrGatewayAccount } from "./gateway.js";
|
|
5
|
-
import { setNostrRuntime } from "./runtime.js";
|
|
6
|
-
import { buildResolvedNostrAccount } from "./test-fixtures.js";
|
|
7
|
-
|
|
8
|
-
const mocks = vi.hoisted(() => ({
|
|
9
|
-
normalizePubkey: vi.fn((value: string) =>
|
|
10
|
-
value
|
|
11
|
-
.trim()
|
|
12
|
-
.replace(/^nostr:/i, "")
|
|
13
|
-
.toLowerCase(),
|
|
14
|
-
),
|
|
15
|
-
startNostrBus: vi.fn(),
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
vi.mock("./nostr-bus.js", () => ({
|
|
19
|
-
DEFAULT_RELAYS: ["wss://relay.example.com"],
|
|
20
|
-
startNostrBus: mocks.startNostrBus,
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
vi.mock("./nostr-key-utils.js", () => ({
|
|
24
|
-
getPublicKeyFromPrivate: vi.fn(() => "bot-pubkey"),
|
|
25
|
-
normalizePubkey: mocks.normalizePubkey,
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
function createMockBus() {
|
|
29
|
-
return {
|
|
30
|
-
sendDm: vi.fn(async () => {}),
|
|
31
|
-
close: vi.fn(),
|
|
32
|
-
getMetrics: vi.fn(() => ({ counters: {} })),
|
|
33
|
-
publishProfile: vi.fn(),
|
|
34
|
-
getProfileState: vi.fn(async () => null),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function createRuntimeHarness() {
|
|
39
|
-
const recordInboundSession = vi.fn(async () => {});
|
|
40
|
-
const dispatchReplyWithBufferedBlockDispatcher = vi.fn(async ({ dispatcherOptions }) => {
|
|
41
|
-
await dispatcherOptions.deliver({ text: "|a|b|" });
|
|
42
|
-
});
|
|
43
|
-
const runtime = {
|
|
44
|
-
channel: {
|
|
45
|
-
text: {
|
|
46
|
-
resolveMarkdownTableMode: vi.fn(() => "off"),
|
|
47
|
-
convertMarkdownTables: vi.fn((text: string) => `converted:${text}`),
|
|
48
|
-
},
|
|
49
|
-
commands: {
|
|
50
|
-
shouldComputeCommandAuthorized: vi.fn(() => true),
|
|
51
|
-
resolveCommandAuthorizedFromAuthorizers: vi.fn(() => true),
|
|
52
|
-
},
|
|
53
|
-
routing: {
|
|
54
|
-
resolveAgentRoute: vi.fn(({ accountId, peer }) => ({
|
|
55
|
-
agentId: "agent-nostr",
|
|
56
|
-
accountId,
|
|
57
|
-
sessionKey: `nostr:${peer.id}`,
|
|
58
|
-
})),
|
|
59
|
-
},
|
|
60
|
-
session: {
|
|
61
|
-
resolveStorePath: vi.fn(() => "/tmp/nostr-session-store"),
|
|
62
|
-
readSessionUpdatedAt: vi.fn(() => undefined),
|
|
63
|
-
recordInboundSession,
|
|
64
|
-
},
|
|
65
|
-
reply: {
|
|
66
|
-
formatAgentEnvelope: vi.fn(({ body }) => `envelope:${body}`),
|
|
67
|
-
resolveEnvelopeFormatOptions: vi.fn(() => ({ mode: "agent" })),
|
|
68
|
-
finalizeInboundContext: vi.fn((ctx) => ctx),
|
|
69
|
-
dispatchReplyWithBufferedBlockDispatcher,
|
|
70
|
-
},
|
|
71
|
-
pairing: {
|
|
72
|
-
readAllowFromStore: vi.fn(async () => []),
|
|
73
|
-
upsertPairingRequest: vi.fn(async () => ({ code: "PAIR1234", created: true })),
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
} as unknown as PluginRuntime;
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
runtime,
|
|
80
|
-
recordInboundSession,
|
|
81
|
-
dispatchReplyWithBufferedBlockDispatcher,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function startGatewayHarness(params: {
|
|
86
|
-
account: ReturnType<typeof buildResolvedNostrAccount>;
|
|
87
|
-
cfg?: Parameters<typeof createStartAccountContext>[0]["cfg"];
|
|
88
|
-
}) {
|
|
89
|
-
const harness = createRuntimeHarness();
|
|
90
|
-
const bus = createMockBus();
|
|
91
|
-
setNostrRuntime(harness.runtime);
|
|
92
|
-
mocks.startNostrBus.mockResolvedValueOnce(bus as never);
|
|
93
|
-
|
|
94
|
-
const cleanup = (await startNostrGatewayAccount(
|
|
95
|
-
createStartAccountContext({
|
|
96
|
-
account: params.account,
|
|
97
|
-
cfg: params.cfg,
|
|
98
|
-
}),
|
|
99
|
-
)) as { stop: () => void };
|
|
100
|
-
|
|
101
|
-
return { harness, bus, cleanup };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
describe("nostr inbound gateway path", () => {
|
|
105
|
-
afterEach(() => {
|
|
106
|
-
mocks.normalizePubkey.mockClear();
|
|
107
|
-
mocks.startNostrBus.mockReset();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("issues a pairing reply before decrypt for unknown senders", async () => {
|
|
111
|
-
const { cleanup } = await startGatewayHarness({
|
|
112
|
-
account: buildResolvedNostrAccount({
|
|
113
|
-
config: { dmPolicy: "pairing", allowFrom: [] },
|
|
114
|
-
}),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const options = mocks.startNostrBus.mock.calls[0]?.[0] as {
|
|
118
|
-
authorizeSender: (params: {
|
|
119
|
-
senderPubkey: string;
|
|
120
|
-
reply: (text: string) => Promise<void>;
|
|
121
|
-
}) => Promise<string>;
|
|
122
|
-
};
|
|
123
|
-
const sendPairingReply = vi.fn(async (_text: string) => {});
|
|
124
|
-
|
|
125
|
-
await expect(
|
|
126
|
-
options.authorizeSender({
|
|
127
|
-
senderPubkey: "nostr:UNKNOWN-SENDER",
|
|
128
|
-
reply: sendPairingReply,
|
|
129
|
-
}),
|
|
130
|
-
).resolves.toBe("pairing");
|
|
131
|
-
expect(sendPairingReply).toHaveBeenCalledTimes(1);
|
|
132
|
-
expect(sendPairingReply.mock.calls[0]?.[0]).toContain("Pairing code:");
|
|
133
|
-
|
|
134
|
-
cleanup.stop();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("routes allowed DMs through the standard reply pipeline", async () => {
|
|
138
|
-
const { harness, cleanup } = await startGatewayHarness({
|
|
139
|
-
account: buildResolvedNostrAccount({
|
|
140
|
-
publicKey: "bot-pubkey",
|
|
141
|
-
config: { dmPolicy: "allowlist", allowFrom: ["nostr:sender-pubkey"] },
|
|
142
|
-
}),
|
|
143
|
-
cfg: {
|
|
144
|
-
session: { store: { type: "jsonl" } },
|
|
145
|
-
commands: { useAccessGroups: true },
|
|
146
|
-
} as never,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const options = mocks.startNostrBus.mock.calls[0]?.[0] as {
|
|
150
|
-
onMessage: (
|
|
151
|
-
senderPubkey: string,
|
|
152
|
-
text: string,
|
|
153
|
-
reply: (text: string) => Promise<void>,
|
|
154
|
-
meta: { eventId: string; createdAt: number },
|
|
155
|
-
) => Promise<void>;
|
|
156
|
-
};
|
|
157
|
-
const sendReply = vi.fn(async (_text: string) => {});
|
|
158
|
-
|
|
159
|
-
await options.onMessage("sender-pubkey", "hello from nostr", sendReply, {
|
|
160
|
-
eventId: "event-123",
|
|
161
|
-
createdAt: 1_710_000_000,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
expect(harness.recordInboundSession).toHaveBeenCalledTimes(1);
|
|
165
|
-
expect(harness.dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledTimes(1);
|
|
166
|
-
expect(harness.dispatchReplyWithBufferedBlockDispatcher.mock.calls[0]?.[0]?.ctx).toMatchObject({
|
|
167
|
-
BodyForAgent: "hello from nostr",
|
|
168
|
-
SenderId: "sender-pubkey",
|
|
169
|
-
MessageSid: "event-123",
|
|
170
|
-
CommandAuthorized: true,
|
|
171
|
-
});
|
|
172
|
-
expect(sendReply).toHaveBeenCalledWith("converted:|a|b|");
|
|
173
|
-
|
|
174
|
-
cleanup.stop();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-helpers";
|
|
2
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
|
3
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
-
import type { PluginRuntime } from "../runtime-api.js";
|
|
5
|
-
import { nostrOutboundAdapter, startNostrGatewayAccount } from "./gateway.js";
|
|
6
|
-
import { setNostrRuntime } from "./runtime.js";
|
|
7
|
-
import { TEST_RESOLVED_PRIVATE_KEY, buildResolvedNostrAccount } from "./test-fixtures.js";
|
|
8
|
-
|
|
9
|
-
const mocks = vi.hoisted(() => ({
|
|
10
|
-
normalizePubkey: vi.fn((value: string) => `normalized-${value.toLowerCase()}`),
|
|
11
|
-
startNostrBus: vi.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
vi.mock("./nostr-bus.js", () => ({
|
|
15
|
-
DEFAULT_RELAYS: ["wss://relay.example.com"],
|
|
16
|
-
startNostrBus: mocks.startNostrBus,
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
vi.mock("./nostr-key-utils.js", () => ({
|
|
20
|
-
getPublicKeyFromPrivate: vi.fn(() => "pubkey"),
|
|
21
|
-
normalizePubkey: mocks.normalizePubkey,
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
function createCfg() {
|
|
25
|
-
return {
|
|
26
|
-
channels: {
|
|
27
|
-
nostr: {
|
|
28
|
-
privateKey: TEST_RESOLVED_PRIVATE_KEY, // pragma: allowlist secret
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function installOutboundRuntime(convertMarkdownTables = vi.fn((text: string) => text)) {
|
|
35
|
-
const resolveMarkdownTableMode = vi.fn(() => "off");
|
|
36
|
-
setNostrRuntime({
|
|
37
|
-
channel: {
|
|
38
|
-
text: {
|
|
39
|
-
resolveMarkdownTableMode,
|
|
40
|
-
convertMarkdownTables,
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
reply: {},
|
|
44
|
-
} as unknown as PluginRuntime);
|
|
45
|
-
return { resolveMarkdownTableMode, convertMarkdownTables };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function startOutboundAccount(accountId?: string) {
|
|
49
|
-
const sendDm = vi.fn(async () => {});
|
|
50
|
-
const bus = {
|
|
51
|
-
sendDm,
|
|
52
|
-
close: vi.fn(),
|
|
53
|
-
getMetrics: vi.fn(() => ({ counters: {} })),
|
|
54
|
-
publishProfile: vi.fn(),
|
|
55
|
-
getProfileState: vi.fn(async () => null),
|
|
56
|
-
};
|
|
57
|
-
mocks.startNostrBus.mockResolvedValueOnce(bus as unknown);
|
|
58
|
-
|
|
59
|
-
const cleanup = (await startNostrGatewayAccount(
|
|
60
|
-
createStartAccountContext({
|
|
61
|
-
account: buildResolvedNostrAccount(accountId ? { accountId } : undefined),
|
|
62
|
-
}),
|
|
63
|
-
)) as { stop: () => void };
|
|
64
|
-
|
|
65
|
-
return { cleanup, sendDm };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
describe("nostr outbound cfg threading", () => {
|
|
69
|
-
afterEach(() => {
|
|
70
|
-
mocks.normalizePubkey.mockClear();
|
|
71
|
-
mocks.startNostrBus.mockReset();
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("uses resolved cfg when converting markdown tables before send", async () => {
|
|
75
|
-
const { resolveMarkdownTableMode, convertMarkdownTables } = installOutboundRuntime(
|
|
76
|
-
vi.fn((text: string) => `converted:${text}`),
|
|
77
|
-
);
|
|
78
|
-
const { cleanup, sendDm } = await startOutboundAccount();
|
|
79
|
-
|
|
80
|
-
const cfg = createCfg();
|
|
81
|
-
await nostrOutboundAdapter.sendText({
|
|
82
|
-
cfg: cfg as OpenClawConfig,
|
|
83
|
-
to: "NPUB123",
|
|
84
|
-
text: "|a|b|",
|
|
85
|
-
accountId: "default",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
expect(resolveMarkdownTableMode).toHaveBeenCalledWith({
|
|
89
|
-
cfg,
|
|
90
|
-
channel: "nostr",
|
|
91
|
-
accountId: "default",
|
|
92
|
-
});
|
|
93
|
-
expect(convertMarkdownTables).toHaveBeenCalledWith("|a|b|", "off");
|
|
94
|
-
expect(mocks.normalizePubkey).toHaveBeenCalledWith("NPUB123");
|
|
95
|
-
expect(sendDm).toHaveBeenCalledWith("normalized-npub123", "converted:|a|b|");
|
|
96
|
-
|
|
97
|
-
cleanup.stop();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("uses the configured defaultAccount when accountId is omitted", async () => {
|
|
101
|
-
const { resolveMarkdownTableMode } = installOutboundRuntime();
|
|
102
|
-
const { cleanup, sendDm } = await startOutboundAccount("work");
|
|
103
|
-
|
|
104
|
-
const cfg = {
|
|
105
|
-
channels: {
|
|
106
|
-
nostr: {
|
|
107
|
-
privateKey: TEST_RESOLVED_PRIVATE_KEY, // pragma: allowlist secret
|
|
108
|
-
defaultAccount: "work",
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
await nostrOutboundAdapter.sendText({
|
|
114
|
-
cfg: cfg as OpenClawConfig,
|
|
115
|
-
to: "NPUB123",
|
|
116
|
-
text: "hello",
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
expect(resolveMarkdownTableMode).toHaveBeenCalledWith({
|
|
120
|
-
cfg,
|
|
121
|
-
channel: "nostr",
|
|
122
|
-
accountId: "work",
|
|
123
|
-
});
|
|
124
|
-
expect(sendDm).toHaveBeenCalledWith("normalized-npub123", "hello");
|
|
125
|
-
|
|
126
|
-
cleanup.stop();
|
|
127
|
-
});
|
|
128
|
-
});
|