@openclaw/nostr 2026.8.1-beta.2 → 2026.8.1-beta.3

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 CHANGED
@@ -1,14 +1,14 @@
1
- import { s as resolveNostrAccount } from "./setup-surface-DAhyNOcb.js";
1
+ import { s as resolveNostrAccount } from "./setup-surface-uA4hN27A.js";
2
2
  import { getPluginRuntimeGatewayRequestScope } from "./runtime-api.js";
3
3
  import { n as NostrProfileSchema } from "./config-schema-CkYd8ACn.js";
4
- import { a as getNostrRuntime, i as contentToProfile, n as nostrPlugin, o as setNostrRuntime, r as publishNostrProfile, t as getNostrProfileState } from "./channel-BifOvAwp.js";
4
+ import { a as getNostrRuntime, i as contentToProfile, n as nostrPlugin, o as setNostrRuntime, r as publishNostrProfile, t as getNostrProfileState } from "./channel-ity6au4a.js";
5
5
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, readStringValue } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import { z } from "zod";
7
7
  import { SimplePool } from "nostr-tools";
8
+ import { createFixedWindowRateLimiter as createFixedWindowRateLimiter$1 } from "openclaw/plugin-sdk/webhook-ingress";
8
9
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
9
10
  import { isBlockedHostnameOrIp, isLoopbackHost } from "openclaw/plugin-sdk/ssrf-runtime";
10
11
  import { readJsonBodyWithLimit, requestBodyErrorToText } from "openclaw/plugin-sdk/webhook-request-guards";
11
- import { createFixedWindowRateLimiter } from "openclaw/plugin-sdk/webhook-ingress";
12
12
  import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
13
13
  //#region extensions/nostr/src/nostr-profile-url-safety.ts
14
14
  function validateUrlSafety(urlStr) {
@@ -184,7 +184,7 @@ function mergeProfiles(local, imported) {
184
184
  }
185
185
  //#endregion
186
186
  //#region extensions/nostr/src/nostr-profile-http.ts
187
- const profileRateLimiter = createFixedWindowRateLimiter({
187
+ const profileRateLimiter = createFixedWindowRateLimiter$1({
188
188
  windowMs: 6e4,
189
189
  maxRequests: 5,
190
190
  maxTrackedKeys: 2048
@@ -1,6 +1,6 @@
1
- import { a as listNostrAccountIds, c as normalizePubkey, l as validatePrivateKey, n as nostrSetupContract, o as resolveDefaultNostrAccountId, r as nostrSetupWizard, s as resolveNostrAccount } from "./setup-surface-DAhyNOcb.js";
1
+ import { a as listNostrAccountIds, c as normalizePubkey, l as validatePrivateKey, n as nostrSetupContract, o as resolveDefaultNostrAccountId, r as nostrSetupWizard, s as resolveNostrAccount } from "./setup-surface-uA4hN27A.js";
2
2
  import { a as collectStatusIssuesFromLastError, i as buildChannelConfigSchema, n as NostrProfileSchema, o as createDefaultChannelRuntimeState, r as DEFAULT_ACCOUNT_ID, s as formatPairingApproveHint, t as NostrConfigSchema } from "./config-schema-CkYd8ACn.js";
3
- import { s as DEFAULT_RELAYS } from "./setup-adapter-DLthHa2q.js";
3
+ import { c as DEFAULT_RELAYS } from "./setup-adapter-ClKW2fkz.js";
4
4
  import { t as normalizeNostrStateAccountId } from "./state-account-id-CvBZ9s6P.js";
5
5
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
6
6
  import { createScopedDmSecurityResolver, createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
@@ -18,6 +18,9 @@ import { chunkTextForOutbound, sanitizeAssistantVisibleText, stripMarkdown } fro
18
18
  import { SimplePool, finalizeEvent, getPublicKey, verifyEvent } from "nostr-tools";
19
19
  import { decrypt, encrypt } from "nostr-tools/nip04";
20
20
  import { createDirectDmPreCryptoGuardPolicy } from "openclaw/plugin-sdk/direct-dm-guard-policy";
21
+ import { createFixedWindowRateLimiter } from "openclaw/plugin-sdk/webhook-ingress";
22
+ import { retryAsync } from "openclaw/plugin-sdk/retry-runtime";
23
+ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
21
24
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
22
25
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
23
26
  import { buildChannelOutboundSessionRoute, stripChannelTargetPrefix } from "openclaw/plugin-sdk/core";
@@ -289,19 +292,16 @@ function createNostrCursorStateWriter(options) {
289
292
  let activeFlush;
290
293
  let recoveryFlush;
291
294
  const writeWithRetry = async (cursor) => {
292
- let lastError;
293
- for (const delayMs of CURSOR_WRITE_RETRY_MS) {
294
- if (delayMs > 0) await new Promise((resolve) => {
295
- setTimeout(resolve, delayMs);
295
+ try {
296
+ await retryAsync(() => options.write(cursor), {
297
+ attempts: CURSOR_WRITE_RETRY_MS.length,
298
+ minDelayMs: 0,
299
+ delayMs: ({ attempt }) => CURSOR_WRITE_RETRY_MS[attempt] ?? 0,
300
+ sleep: (delayMs) => sleepWithAbort(delayMs)
296
301
  });
297
- try {
298
- await options.write(cursor);
299
- return;
300
- } catch (error) {
301
- lastError = error;
302
- }
302
+ } catch (error) {
303
+ throw new Error("Nostr cursor state write failed.", { cause: error });
303
304
  }
304
- throw new Error("Nostr cursor state write failed.", { cause: lastError });
305
305
  };
306
306
  const enqueueWrite = (cursor) => {
307
307
  const write = writeTail.then(() => writeWithRetry(cursor));
@@ -548,12 +548,8 @@ function createNostrIngress(options) {
548
548
  const pending = await getQueue().listPending({ limit: options.maxPendingEvents });
549
549
  const claims = await getQueue().listClaims();
550
550
  if (pending.length + claims.length >= options.maxPendingEvents) throw new NostrIngressAdmissionRejectedError("backpressure", "Nostr event exceeds the durable ingress backlog.");
551
- let lastError;
552
- for (const delayMs of NOSTR_INGRESS_APPEND_RETRY_MS) {
553
- if (delayMs > 0) await new Promise((resolve) => {
554
- setTimeout(resolve, delayMs);
555
- });
556
- try {
551
+ try {
552
+ return await retryAsync(async () => {
557
553
  const result = await getQueue().enqueue(prepared.facts.eventId, prepared.payload, {
558
554
  receivedAt: prepared.receivedAt,
559
555
  laneKey: prepared.facts.laneKey
@@ -561,11 +557,15 @@ function createNostrIngress(options) {
561
557
  options.afterDurableAppend(prepared.event);
562
558
  monitor.requestDrain();
563
559
  return result.kind === "accepted" ? "accepted" : "duplicate";
564
- } catch (error) {
565
- lastError = error;
566
- }
560
+ }, {
561
+ attempts: NOSTR_INGRESS_APPEND_RETRY_MS.length,
562
+ minDelayMs: 0,
563
+ delayMs: ({ attempt }) => NOSTR_INGRESS_APPEND_RETRY_MS[attempt] ?? 0,
564
+ sleep: (delayMs) => sleepWithAbort(delayMs)
565
+ });
566
+ } catch (error) {
567
+ throw new Error(`Nostr durable admission failed: ${formatErrorMessage(error)}`, { cause: error });
567
568
  }
568
- throw new Error(`Nostr durable admission failed: ${formatErrorMessage(lastError)}`, { cause: lastError });
569
569
  };
570
570
  return {
571
571
  ready: async () => {
@@ -742,48 +742,6 @@ async function publishProfile(pool, sk, relays, profile, lastPublishedAt) {
742
742
  return publishProfileEvent(pool, relays, createProfileEvent(sk, profile, lastPublishedAt));
743
743
  }
744
744
  //#endregion
745
- //#region extensions/nostr/src/nostr-rate-limiter.ts
746
- function createFixedWindowRateLimiter(params) {
747
- const windowMs = Math.max(1, Math.floor(params.windowMs));
748
- const maxRequests = Math.max(1, Math.floor(params.maxRequests));
749
- const maxTrackedKeys = Math.max(1, Math.floor(params.maxTrackedKeys));
750
- const state = /* @__PURE__ */ new Map();
751
- const touch = (key, value) => {
752
- state.delete(key);
753
- state.set(key, value);
754
- };
755
- const prune = (nowMs) => {
756
- for (const [key, entry] of state) if (nowMs - entry.windowStartMs >= windowMs) state.delete(key);
757
- while (state.size > maxTrackedKeys) {
758
- const oldest = state.keys().next().value;
759
- if (!oldest) break;
760
- state.delete(oldest);
761
- }
762
- };
763
- return {
764
- isRateLimited: (key, nowMs = Date.now()) => {
765
- if (!key) return false;
766
- prune(nowMs);
767
- const existing = state.get(key);
768
- if (!existing || nowMs - existing.windowStartMs >= windowMs) {
769
- touch(key, {
770
- count: 1,
771
- windowStartMs: nowMs
772
- });
773
- return false;
774
- }
775
- const nextCount = existing.count + 1;
776
- touch(key, {
777
- count: nextCount,
778
- windowStartMs: existing.windowStartMs
779
- });
780
- return nextCount > maxRequests;
781
- },
782
- size: () => state.size,
783
- clear: () => state.clear()
784
- };
785
- }
786
- //#endregion
787
745
  //#region extensions/nostr/src/nostr-relay-subscription.ts
788
746
  const DEFAULT_EOSE_CONFIRM_DEADLINE_MS = 1e4;
789
747
  const LIBRARY_EOSE_TIMEOUT_MARGIN_MS = 1e3;
@@ -1357,7 +1315,6 @@ async function sendEncryptedDm(pool, sk, toPubkey, text, relays, metrics, circui
1357
1315
  //#endregion
1358
1316
  //#region extensions/nostr/src/gateway.ts
1359
1317
  const activeBuses = /* @__PURE__ */ new Map();
1360
- const metricsSnapshots = /* @__PURE__ */ new Map();
1361
1318
  const ACCESS_GROUP_PREFIX = "accessGroup:";
1362
1319
  function normalizeRelayLifecycleKey(relay) {
1363
1320
  return new URL(relay).toString();
@@ -1426,7 +1383,6 @@ const startNostrGatewayAccount = async (ctx) => {
1426
1383
  allowFrom: account.config.allowFrom,
1427
1384
  command: runtime.channel.commands.shouldComputeCommandAuthorized(rawBody, ctx.cfg) ? { modeWhenAccessGroupsOff: "configured" } : void 0
1428
1385
  });
1429
- let busHandle = null;
1430
1386
  const connectedRelays = /* @__PURE__ */ new Set();
1431
1387
  const authorizeSender = async (input) => {
1432
1388
  const resolved = await resolveInboundAccess(input.senderId, "");
@@ -1534,17 +1490,13 @@ const startNostrGatewayAccount = async (ctx) => {
1534
1490
  else if (event.name === "relay.circuit_breaker.open") ctx.log?.warn?.(`[${account.accountId}] Circuit breaker opened for relay: ${event.labels?.relay}`);
1535
1491
  else if (event.name === "relay.circuit_breaker.close") ctx.log?.info?.(`[${account.accountId}] Circuit breaker closed for relay: ${event.labels?.relay}`);
1536
1492
  else if (event.name === "relay.error") ctx.log?.debug?.(`[${account.accountId}] Relay error: ${event.labels?.relay}`);
1537
- if (busHandle) metricsSnapshots.set(account.accountId, busHandle.getMetrics());
1538
1493
  }
1539
1494
  });
1540
- busHandle = bus;
1541
1495
  activeBuses.set(account.accountId, bus);
1542
1496
  ctx.log?.info?.(`[${account.accountId}] Nostr provider started with ${account.relays.length} configured relay(s)`);
1543
1497
  return { stop: async () => {
1544
- await bus.close();
1545
- if (busHandle === bus) busHandle = null;
1546
1498
  if (activeBuses.get(account.accountId) === bus) activeBuses.delete(account.accountId);
1547
- metricsSnapshots.delete(account.accountId);
1499
+ await bus.close();
1548
1500
  ctx.log?.info?.(`[${account.accountId}] Nostr provider stopped`);
1549
1501
  } };
1550
1502
  },
@@ -1,2 +1,2 @@
1
- import { n as nostrPlugin } from "./channel-BifOvAwp.js";
1
+ import { n as nostrPlugin } from "./channel-ity6au4a.js";
2
2
  export { nostrPlugin };
package/dist/index.js CHANGED
@@ -27,6 +27,10 @@ var nostr_default = defineBundledChannelEntry({
27
27
  specifier: "./channel-plugin-api.js",
28
28
  exportName: "nostrPlugin"
29
29
  },
30
+ secrets: {
31
+ specifier: "./secret-contract-api.js",
32
+ exportName: "channelSecrets"
33
+ },
30
34
  runtime: {
31
35
  specifier: "./api.js",
32
36
  exportName: "setNostrRuntime"
@@ -0,0 +1,37 @@
1
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
+ import { collectSecretInputAssignment, createChannelSecretTargetRegistryEntries, getChannelRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
3
+ //#region extensions/nostr/src/secret-contract.ts
4
+ const secretTargetRegistryEntries = createChannelSecretTargetRegistryEntries({
5
+ channelKey: "nostr",
6
+ channel: ["privateKey"]
7
+ });
8
+ function collectRuntimeConfigAssignments(params) {
9
+ const nostr = getChannelRecord(params.config, "nostr");
10
+ if (!nostr) return;
11
+ const accountId = normalizeAccountId(typeof nostr.defaultAccount === "string" ? nostr.defaultAccount : void 0);
12
+ collectSecretInputAssignment({
13
+ value: nostr.privateKey,
14
+ path: "channels.nostr.privateKey",
15
+ expected: "string",
16
+ defaults: params.defaults,
17
+ context: params.context,
18
+ active: nostr.enabled !== false,
19
+ inactiveReason: "Nostr channel is disabled.",
20
+ owner: {
21
+ ownerKind: "account",
22
+ ownerId: `nostr:${accountId}`,
23
+ requiredForGateway: false,
24
+ disposition: "isolate",
25
+ contract: nostr
26
+ },
27
+ apply: (value) => {
28
+ nostr.privateKey = value;
29
+ }
30
+ });
31
+ }
32
+ const channelSecrets = {
33
+ secretTargetRegistryEntries,
34
+ collectRuntimeConfigAssignments
35
+ };
36
+ //#endregion
37
+ export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -8,6 +8,9 @@ const DEFAULT_RELAYS = ["wss://relay.damus.io", "wss://nos.lol"];
8
8
  //#endregion
9
9
  //#region extensions/nostr/src/private-key.ts
10
10
  const NOSTR_PRIVATE_KEY_ENV_VAR = "NOSTR_PRIVATE_KEY";
11
+ function hasConfiguredNostrPrivateKey(value) {
12
+ return hasConfiguredSecretInput(value) || Boolean(process.env["NOSTR_PRIVATE_KEY"]?.trim());
13
+ }
11
14
  function resolveNostrPrivateKey(value) {
12
15
  const configured = normalizeSecretInputString(value);
13
16
  if (configured || hasConfiguredSecretInput(value)) return configured ?? "";
@@ -124,4 +127,4 @@ function createNostrSetupStatus(resolveAccount) {
124
127
  });
125
128
  }
126
129
  //#endregion
127
- export { parseRelayUrls as a, createNostrSetupStatus as i, createNostrSetupAdapter as n, resolveNostrPrivateKey as o, createNostrSetupContract as r, DEFAULT_RELAYS as s, buildNostrSetupPatch as t };
130
+ export { parseRelayUrls as a, DEFAULT_RELAYS as c, createNostrSetupStatus as i, createNostrSetupAdapter as n, hasConfiguredNostrPrivateKey as o, createNostrSetupContract as r, resolveNostrPrivateKey as s, buildNostrSetupPatch as t };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { r as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-DAhyNOcb.js";
1
+ import { r as nostrSetupWizard, t as nostrSetupAdapter } from "./setup-surface-uA4hN27A.js";
2
2
  export { nostrSetupAdapter, nostrSetupWizard };
@@ -5,6 +5,10 @@ var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  plugin: {
6
6
  specifier: "./setup-plugin-api.js",
7
7
  exportName: "nostrSetupPlugin"
8
+ },
9
+ secrets: {
10
+ specifier: "./secret-contract-api.js",
11
+ exportName: "channelSecrets"
8
12
  }
9
13
  });
10
14
  //#endregion
@@ -1,21 +1,21 @@
1
1
  import { i as buildChannelConfigSchema, t as NostrConfigSchema } from "./config-schema-CkYd8ACn.js";
2
- import { i as createNostrSetupStatus, n as createNostrSetupAdapter, o as resolveNostrPrivateKey, r as createNostrSetupContract, s as DEFAULT_RELAYS } from "./setup-adapter-DLthHa2q.js";
2
+ import { c as DEFAULT_RELAYS, i as createNostrSetupStatus, n as createNostrSetupAdapter, o as hasConfiguredNostrPrivateKey, r as createNostrSetupContract, s as resolveNostrPrivateKey } from "./setup-adapter-ClKW2fkz.js";
3
3
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
4
- import { DEFAULT_ACCOUNT_ID, createDelegatedSetupWizardProxy } from "openclaw/plugin-sdk/setup-runtime";
4
+ import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
5
+ import { createDelegatedSetupWizardProxy } from "openclaw/plugin-sdk/setup-runtime";
5
6
  //#region extensions/nostr/src/channel.setup.ts
6
7
  const channel = "nostr";
7
8
  function getNostrConfig(cfg) {
8
9
  return cfg.channels?.nostr;
9
10
  }
10
11
  function resolveDefaultSetupNostrAccountId(cfg) {
11
- const configured = getNostrConfig(cfg)?.defaultAccount;
12
- return typeof configured === "string" && configured.trim() ? configured.trim() : DEFAULT_ACCOUNT_ID;
12
+ return normalizeAccountId(getNostrConfig(cfg)?.defaultAccount);
13
13
  }
14
14
  function resolveSetupNostrAccount(params) {
15
15
  const nostrCfg = getNostrConfig(params.cfg);
16
- const accountId = params.accountId?.trim() || resolveDefaultSetupNostrAccountId(params.cfg);
16
+ const accountId = normalizeAccountId(params.accountId ?? resolveDefaultSetupNostrAccountId(params.cfg));
17
17
  const privateKey = resolveNostrPrivateKey(nostrCfg?.privateKey);
18
- const configured = Boolean(privateKey);
18
+ const configured = hasConfiguredNostrPrivateKey(nostrCfg?.privateKey);
19
19
  return {
20
20
  accountId,
21
21
  name: typeof nostrCfg?.name === "string" ? nostrCfg.name : void 0,
@@ -38,7 +38,7 @@ function resolveSetupNostrAccount(params) {
38
38
  }
39
39
  const nostrSetupWizard = createDelegatedSetupWizardProxy({
40
40
  channel,
41
- loadWizard: async () => (await import("./setup-surface-DAhyNOcb.js").then((n) => n.i)).nostrSetupWizard,
41
+ loadWizard: async () => (await import("./setup-surface-uA4hN27A.js").then((n) => n.i)).nostrSetupWizard,
42
42
  status: createNostrSetupStatus(resolveSetupNostrAccount),
43
43
  resolveShouldPromptAccountIds: () => false,
44
44
  delegatePrepare: true,
@@ -1,4 +1,4 @@
1
- import { a as parseRelayUrls, i as createNostrSetupStatus, n as createNostrSetupAdapter, o as resolveNostrPrivateKey, r as createNostrSetupContract, s as DEFAULT_RELAYS, t as buildNostrSetupPatch } from "./setup-adapter-DLthHa2q.js";
1
+ import { a as parseRelayUrls, c as DEFAULT_RELAYS, i as createNostrSetupStatus, n as createNostrSetupAdapter, o as hasConfiguredNostrPrivateKey, r as createNostrSetupContract, s as resolveNostrPrivateKey, t as buildNostrSetupPatch } from "./setup-adapter-ClKW2fkz.js";
2
2
  import { createAccountListHelpers } from "openclaw/plugin-sdk/account-helpers";
3
3
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { hasConfiguredSecretInput, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
@@ -59,7 +59,7 @@ const { listAccountIds: listNostrAccountIds, resolveDefaultAccountId: resolveDef
59
59
  fallbackAccountIdWhenEmpty: false,
60
60
  resolveImplicitAccountId: (cfg) => {
61
61
  const account = cfg.channels?.nostr;
62
- return resolveNostrPrivateKey(account?.privateKey) ? normalizeOptionalAccountId(account?.defaultAccount) ?? DEFAULT_ACCOUNT_ID : void 0;
62
+ return hasConfiguredNostrPrivateKey(account?.privateKey) ? normalizeOptionalAccountId(account?.defaultAccount) ?? DEFAULT_ACCOUNT_ID : void 0;
63
63
  }
64
64
  });
65
65
  /**
@@ -70,7 +70,7 @@ function resolveNostrAccount(opts) {
70
70
  const nostrCfg = opts.cfg.channels?.nostr;
71
71
  const baseEnabled = nostrCfg?.enabled !== false;
72
72
  const privateKey = resolveNostrPrivateKey(nostrCfg?.privateKey);
73
- const configured = Boolean(privateKey);
73
+ const configured = hasConfiguredNostrPrivateKey(nostrCfg?.privateKey);
74
74
  let publicKey = "";
75
75
  if (privateKey) try {
76
76
  publicKey = getPublicKeyFromPrivate(privateKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/nostr",
3
- "version": "2026.8.1-beta.2",
3
+ "version": "2026.8.1-beta.3",
4
4
  "description": "OpenClaw Nostr channel plugin for NIP-04 encrypted direct messages.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -12,7 +12,7 @@
12
12
  "zod": "4.4.3"
13
13
  },
14
14
  "peerDependencies": {
15
- "openclaw": ">=2026.8.1-beta.2"
15
+ "openclaw": ">=2026.8.1-beta.3"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "openclaw": {
@@ -79,10 +79,10 @@
79
79
  "minHostVersion": ">=2026.4.10"
80
80
  },
81
81
  "compat": {
82
- "pluginApi": ">=2026.8.1-beta.2"
82
+ "pluginApi": ">=2026.8.1-beta.3"
83
83
  },
84
84
  "build": {
85
- "openclawVersion": "2026.8.1-beta.2"
85
+ "openclawVersion": "2026.8.1-beta.3"
86
86
  },
87
87
  "release": {
88
88
  "publishToClawHub": true,