@openclaw/buzz 2026.8.1-beta.2 → 2026.9.1-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/{adapter.runtime-D0uEmTwf.js → adapter.runtime-DA6guOQ_.js} +5 -5
- package/dist/api.js +2 -2
- package/dist/{channel-CCmyE6Yq.js → channel-BulZqShF.js} +234 -270
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{directory-config-BXa3Vqzu.js → directory-config-BesCse0q.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/qa-runner-api.js +2 -2
- package/dist/{room-membership-CNBlHjTj.js → room-membership-DSAkyE0a.js} +55 -5
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-DCxlD2oY.js → setup-surface-BvQgEJ7O.js} +20 -42
- package/dist/{types-CPFsIZ5Z.js → types-BN_HOsnq.js} +18 -5
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js +7 -5
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js +6 -4
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/core.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/core.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/index.js +17 -11
- package/node_modules/nostr-tools/lib/cjs/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/kinds.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/kinds.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip04.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip10.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip10.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip13.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip17.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip17.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip18.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip18.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip19.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip21.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip22.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip22.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip25.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip25.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip27.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip28.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip28.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip29.js +288 -12
- package/node_modules/nostr-tools/lib/cjs/nip29.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip44.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip45.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip45.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip46.js +9 -5
- package/node_modules/nostr-tools/lib/cjs/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip47.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip47.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip57.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip57.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip59.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip59.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip98.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip98.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nipb0.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nipb7.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nipb7.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/pool.js +9 -5
- package/node_modules/nostr-tools/lib/cjs/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/pure.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/pure.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/references.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/relay.js +8 -4
- package/node_modules/nostr-tools/lib/cjs/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/signer.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/signer.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/utils.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/utils.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/wasm.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/wasm.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js +7 -5
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js +6 -4
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/core.js +10 -6
- package/node_modules/nostr-tools/lib/esm/core.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/index.js +17 -11
- package/node_modules/nostr-tools/lib/esm/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/kinds.js +2 -0
- package/node_modules/nostr-tools/lib/esm/kinds.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip04.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip10.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip10.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip13.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip17.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip17.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip18.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip18.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip19.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip21.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip22.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip22.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip25.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip25.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip27.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip28.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip28.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip29.js +288 -12
- package/node_modules/nostr-tools/lib/esm/nip29.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip44.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip45.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip45.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip46.js +9 -5
- package/node_modules/nostr-tools/lib/esm/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip47.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip47.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip57.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip57.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip59.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip59.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip98.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip98.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nipb0.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nipb7.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nipb7.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/pool.js +9 -5
- package/node_modules/nostr-tools/lib/esm/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/pure.js +10 -6
- package/node_modules/nostr-tools/lib/esm/pure.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/references.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/relay.js +8 -4
- package/node_modules/nostr-tools/lib/esm/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/signer.js +2 -0
- package/node_modules/nostr-tools/lib/esm/signer.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/utils.js +2 -0
- package/node_modules/nostr-tools/lib/esm/utils.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/wasm.js +10 -6
- package/node_modules/nostr-tools/lib/esm/wasm.js.map +2 -2
- package/node_modules/nostr-tools/lib/nostr.bundle.js +17 -11
- package/node_modules/nostr-tools/lib/nostr.bundle.js.map +2 -2
- package/node_modules/nostr-tools/lib/types/core.d.ts +5 -0
- package/node_modules/nostr-tools/lib/types/nip29.d.ts +243 -3
- package/node_modules/nostr-tools/lib/types/nip29.test.d.ts +1 -0
- package/node_modules/nostr-tools/package.json +1 -1
- package/openclaw.plugin.json +24 -0
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { a as BuzzConfigSchema, i as startBuzzRoomMembershipNotifications, n as buzzSetupContract, r as discoverBuzzRooms, t as buzzSetupWizard } from "./setup-surface-
|
|
3
|
-
import { n as listBuzzDirectoryPeersFromConfig, t as listBuzzDirectoryGroupsFromConfig } from "./directory-config-
|
|
4
|
-
import { a as parseBuzzRoomMembershipEvent, c as parseBuzzAuthTag, i as parseBuzzRoomMembershipChangeEvent, l as openBuzzRelaySubscription, n as BUZZ_ROOM_SYSTEM_KIND, o as connectAuthenticatedBuzzRelay, r as isNewerBuzzRoomMembership, s as connectAuthenticatedBuzzRelaySession, t as BUZZ_ROOM_MEMBERSHIP_KIND } from "./room-membership-
|
|
1
|
+
import { a as resolveBuzzPublicKey, c as buildBuzzTarget, d as normalizeBuzzTarget, f as parseBuzzTarget, i as resolveBuzzAccount, l as isConfiguredBuzzChannel, n as decodeBuzzPrivateKey, o as resolveDefaultBuzzAccountId, r as listBuzzAccountIds, t as assertBuzzAccountAvailable, u as looksLikeBuzzTarget } from "./types-BN_HOsnq.js";
|
|
2
|
+
import { a as BuzzConfigSchema, i as startBuzzRoomMembershipNotifications, n as buzzSetupContract, r as discoverBuzzRooms, t as buzzSetupWizard } from "./setup-surface-BvQgEJ7O.js";
|
|
3
|
+
import { n as listBuzzDirectoryPeersFromConfig, t as listBuzzDirectoryGroupsFromConfig } from "./directory-config-BesCse0q.js";
|
|
4
|
+
import { a as parseBuzzRoomMembershipEvent, c as parseBuzzAuthTag, i as parseBuzzRoomMembershipChangeEvent, l as openBuzzRelaySubscription, n as BUZZ_ROOM_SYSTEM_KIND, o as connectAuthenticatedBuzzRelay, r as isNewerBuzzRoomMembership, s as connectAuthenticatedBuzzRelaySession, t as BUZZ_ROOM_MEMBERSHIP_KIND, u as queryBuzzRelaySnapshot } from "./room-membership-DSAkyE0a.js";
|
|
5
5
|
import { a as formatBuzzMessageForAgent, c as inspectBuzzMentionSyntax, i as buildBuzzMessageTags, l as resolveBuzzMessageMentions, n as BUZZ_INBOUND_MESSAGE_KINDS, o as isBuzzInboundMessageKind, r as BUZZ_TYPING_INDICATOR_KIND, s as parseBuzzMessageEvent, t as BUZZ_DIFF_MESSAGE_KIND } from "./message-event-CFw7gGLi.js";
|
|
6
6
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-D19RiJv_.js";
|
|
7
7
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
@@ -11,6 +11,7 @@ import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-run
|
|
|
11
11
|
import { parseThreadSessionSuffix } from "openclaw/plugin-sdk/routing";
|
|
12
12
|
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
13
13
|
import { finalizeEvent } from "nostr-tools";
|
|
14
|
+
import { chunkItems } from "openclaw/plugin-sdk/text-chunking";
|
|
14
15
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
15
16
|
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
16
17
|
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
@@ -20,6 +21,7 @@ import { buildChannelInboundEventContext, resolveChannelInboundRouteEnvelope } f
|
|
|
20
21
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
21
22
|
import { createSubsystemLogger } from "openclaw/plugin-sdk/logging-core";
|
|
22
23
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
23
25
|
const BUZZ_ROOM_METADATA_KIND = 39e3;
|
|
24
26
|
const DEFAULT_BUZZ_DIRECTORY_PROFILE_LIMIT = 2e3;
|
|
25
27
|
const HEX_PUBLIC_KEY_PATTERN = /^[0-9a-f]{64}$/u;
|
|
@@ -250,58 +252,27 @@ const PROFILE_SUBSCRIPTION_REPLACED_REASON = "directory profile subscription rep
|
|
|
250
252
|
const PROFILE_SUBSCRIPTION_FAILED_REASON = "directory profile subscription generation failed";
|
|
251
253
|
const DIRECTORY_SHUTDOWN_REASON = "directory shutdown";
|
|
252
254
|
const DIRECTORY_QUERY_COMPLETE_REASON = "directory query complete";
|
|
253
|
-
const DIRECTORY_QUERY_TIMEOUT_MS = 1e4;
|
|
254
255
|
const PROFILE_SUBSCRIPTION_READY_TIMEOUT_MS = 1e4;
|
|
255
|
-
function chunkValues(values, size) {
|
|
256
|
-
const chunks = [];
|
|
257
|
-
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
258
|
-
return chunks;
|
|
259
|
-
}
|
|
260
256
|
async function queryBuzzDirectoryBatch(params) {
|
|
261
257
|
params.signal?.throwIfAborted();
|
|
262
|
-
await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
params.signal?.removeEventListener("abort", onAbort);
|
|
277
|
-
if (receivedEose) subscriptionRef.current?.close(DIRECTORY_QUERY_COMPLETE_REASON);
|
|
278
|
-
if (error === void 0) resolve();
|
|
279
|
-
else reject(error instanceof Error ? error : new Error("Buzz directory query failed", { cause: error }));
|
|
280
|
-
};
|
|
281
|
-
const onAbort = () => finish(params.signal?.reason ?? /* @__PURE__ */ new Error("Buzz directory query aborted"));
|
|
282
|
-
params.signal?.addEventListener("abort", onAbort, { once: true });
|
|
283
|
-
try {
|
|
284
|
-
subscriptionRef.current = openBuzzRelaySubscription(params.relay, [params.filter], {
|
|
285
|
-
onevent: params.onEvent,
|
|
286
|
-
oneose: () => {
|
|
287
|
-
receivedEose = true;
|
|
288
|
-
if (settled) subscriptionRef.current?.close(DIRECTORY_QUERY_COMPLETE_REASON);
|
|
289
|
-
else finish();
|
|
290
|
-
},
|
|
291
|
-
onclose: (reason) => {
|
|
292
|
-
if (reason !== DIRECTORY_QUERY_COMPLETE_REASON) finish(/* @__PURE__ */ new Error(`Buzz directory query closed: ${reason}`));
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
} catch (error) {
|
|
296
|
-
finish(error);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (settled && receivedEose) subscriptionRef.current.close(DIRECTORY_QUERY_COMPLETE_REASON);
|
|
300
|
-
if (params.signal?.aborted) onAbort();
|
|
258
|
+
await queryBuzzRelaySnapshot({
|
|
259
|
+
relay: params.relay,
|
|
260
|
+
filters: [params.filter],
|
|
261
|
+
signal: params.signal,
|
|
262
|
+
timeoutMessage: "Timed out loading Buzz directory snapshot",
|
|
263
|
+
abortMessage: "Buzz directory query aborted",
|
|
264
|
+
failureMessage: "Buzz directory query failed",
|
|
265
|
+
closeReason: DIRECTORY_QUERY_COMPLETE_REASON,
|
|
266
|
+
closeMessage: (reason) => `Buzz directory query closed: ${reason}`,
|
|
267
|
+
onEvent: params.onEvent,
|
|
268
|
+
result: () => {},
|
|
269
|
+
onTimeout: params.onTimeout,
|
|
270
|
+
closeRelayOnTimeout: !params.onTimeout,
|
|
271
|
+
checkAbortAfterSubscribe: true
|
|
301
272
|
});
|
|
302
273
|
}
|
|
303
274
|
async function queryBuzzDirectoryProfiles(params) {
|
|
304
|
-
for (const authors of
|
|
275
|
+
for (const authors of chunkItems(params.publicKeys, 200)) await queryBuzzDirectoryBatch({
|
|
305
276
|
relay: params.relay,
|
|
306
277
|
filter: {
|
|
307
278
|
kinds: [0],
|
|
@@ -316,7 +287,7 @@ async function queryBuzzDirectoryProfiles(params) {
|
|
|
316
287
|
});
|
|
317
288
|
}
|
|
318
289
|
async function queryBuzzDirectoryRooms(params) {
|
|
319
|
-
for (const roomIds of
|
|
290
|
+
for (const roomIds of chunkItems(params.channelIds, BUZZ_ROOM_QUERY_CHUNK_SIZE)) await queryBuzzDirectoryBatch({
|
|
320
291
|
relay: params.relay,
|
|
321
292
|
filter: {
|
|
322
293
|
kinds: [BUZZ_ROOM_METADATA_KIND],
|
|
@@ -360,7 +331,7 @@ function startBuzzDirectoryRelay(params) {
|
|
|
360
331
|
const publicKeys = queuedProfilePublicKeys;
|
|
361
332
|
queuedProfilePublicKeys = void 0;
|
|
362
333
|
closeProfileGeneration(PROFILE_SUBSCRIPTION_REPLACED_REASON);
|
|
363
|
-
const authorChunks =
|
|
334
|
+
const authorChunks = chunkItems(publicKeys, 200);
|
|
364
335
|
if (authorChunks.length === 0) return;
|
|
365
336
|
const generation = {
|
|
366
337
|
subscriptions: [],
|
|
@@ -473,7 +444,6 @@ const CHANNEL_ADD_POLICIES = /* @__PURE__ */ new Set([
|
|
|
473
444
|
"owner_only",
|
|
474
445
|
"nobody"
|
|
475
446
|
]);
|
|
476
|
-
const PROFILE_QUERY_TIMEOUT_MS = 1e4;
|
|
477
447
|
function parseProfileContent(event) {
|
|
478
448
|
if (!event) return {};
|
|
479
449
|
try {
|
|
@@ -499,33 +469,10 @@ function readNonEmptyString(content, key) {
|
|
|
499
469
|
}
|
|
500
470
|
async function queryCurrentProfiles(params) {
|
|
501
471
|
params.signal?.throwIfAborted();
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
receivedEose: false
|
|
507
|
-
};
|
|
508
|
-
const timeout = setTimeout(() => {
|
|
509
|
-
const error = /* @__PURE__ */ new Error("Timed out loading current Buzz profile");
|
|
510
|
-
finish(error);
|
|
511
|
-
params.onTimeout?.(error);
|
|
512
|
-
params.relay.close();
|
|
513
|
-
}, PROFILE_QUERY_TIMEOUT_MS);
|
|
514
|
-
const finish = (error) => {
|
|
515
|
-
if (state.settled) return;
|
|
516
|
-
state.settled = true;
|
|
517
|
-
clearTimeout(timeout);
|
|
518
|
-
params.signal?.removeEventListener("abort", onAbort);
|
|
519
|
-
if (state.receivedEose) state.subscription?.close("profile query complete");
|
|
520
|
-
if (error !== void 0) {
|
|
521
|
-
reject(error instanceof Error ? error : new Error("Buzz profile query failed", { cause: error }));
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
resolve(latestByKind);
|
|
525
|
-
};
|
|
526
|
-
const onAbort = () => finish(params.signal?.reason ?? /* @__PURE__ */ new Error("Buzz profile query aborted"));
|
|
527
|
-
params.signal?.addEventListener("abort", onAbort, { once: true });
|
|
528
|
-
state.subscription = openBuzzRelaySubscription(params.relay, [{
|
|
472
|
+
const latestByKind = /* @__PURE__ */ new Map();
|
|
473
|
+
return await queryBuzzRelaySnapshot({
|
|
474
|
+
relay: params.relay,
|
|
475
|
+
filters: [{
|
|
529
476
|
kinds: [PROFILE_KIND],
|
|
530
477
|
authors: [params.publicKey],
|
|
531
478
|
limit: 1
|
|
@@ -533,21 +480,19 @@ async function queryCurrentProfiles(params) {
|
|
|
533
480
|
kinds: [AGENT_PROFILE_KIND],
|
|
534
481
|
authors: [params.publicKey],
|
|
535
482
|
limit: 1
|
|
536
|
-
}],
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
});
|
|
550
|
-
if (state.settled && state.receivedEose) state.subscription.close("profile query complete");
|
|
483
|
+
}],
|
|
484
|
+
signal: params.signal,
|
|
485
|
+
timeoutMessage: "Timed out loading current Buzz profile",
|
|
486
|
+
abortMessage: "Buzz profile query aborted",
|
|
487
|
+
failureMessage: "Buzz profile query failed",
|
|
488
|
+
closeReason: "profile query complete",
|
|
489
|
+
closeMessage: (reason) => `Buzz profile query closed: ${reason}`,
|
|
490
|
+
onEvent: (event) => {
|
|
491
|
+
const current = latestByKind.get(event.kind);
|
|
492
|
+
if (!current || event.created_at > current.created_at) latestByKind.set(event.kind, event);
|
|
493
|
+
},
|
|
494
|
+
result: () => latestByKind,
|
|
495
|
+
onTimeout: params.onTimeout
|
|
551
496
|
});
|
|
552
497
|
}
|
|
553
498
|
function buildProfileEvent(params) {
|
|
@@ -733,56 +678,29 @@ function resolveBuzzRoomHistoryLimit(roomCount) {
|
|
|
733
678
|
//#region extensions/buzz/src/room-membership-query.ts
|
|
734
679
|
const RELAY_QUERY_EVENT_LIMIT = 1e3;
|
|
735
680
|
const MEMBERSHIP_QUERY_COMPLETE_REASON = "membership snapshot loaded";
|
|
736
|
-
const MEMBERSHIP_QUERY_TIMEOUT_MS = 1e4;
|
|
737
681
|
async function queryBuzzRoomMembershipBatch(params) {
|
|
738
682
|
const configuredRooms = new Set(params.channelIds);
|
|
739
683
|
const memberships = /* @__PURE__ */ new Map();
|
|
740
|
-
return await
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
params.
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
subscriptionRef.current = openBuzzRelaySubscription(params.relay, [{
|
|
761
|
-
kinds: [BUZZ_ROOM_MEMBERSHIP_KIND],
|
|
762
|
-
authors: [params.relayPublicKey],
|
|
763
|
-
"#d": params.channelIds,
|
|
764
|
-
limit: params.channelIds.length
|
|
765
|
-
}], {
|
|
766
|
-
onevent: (event) => {
|
|
767
|
-
const membership = parseBuzzRoomMembershipEvent(event, params.relayPublicKey);
|
|
768
|
-
if (!membership || !configuredRooms.has(membership.roomId) || !isNewerBuzzRoomMembership(membership, memberships.get(membership.roomId))) return;
|
|
769
|
-
memberships.set(membership.roomId, membership);
|
|
770
|
-
},
|
|
771
|
-
oneose: () => {
|
|
772
|
-
receivedEose = true;
|
|
773
|
-
if (settled) subscriptionRef.current?.close(MEMBERSHIP_QUERY_COMPLETE_REASON);
|
|
774
|
-
else finish();
|
|
775
|
-
},
|
|
776
|
-
onclose: (reason) => {
|
|
777
|
-
if (reason !== MEMBERSHIP_QUERY_COMPLETE_REASON) finish(/* @__PURE__ */ new Error(`Buzz room membership query closed: ${reason}`));
|
|
778
|
-
}
|
|
779
|
-
});
|
|
780
|
-
} catch (error) {
|
|
781
|
-
finish(error);
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
if (settled && receivedEose) subscriptionRef.current.close(MEMBERSHIP_QUERY_COMPLETE_REASON);
|
|
785
|
-
if (params.signal?.aborted) onAbort();
|
|
684
|
+
return await queryBuzzRelaySnapshot({
|
|
685
|
+
relay: params.relay,
|
|
686
|
+
filters: [{
|
|
687
|
+
kinds: [BUZZ_ROOM_MEMBERSHIP_KIND],
|
|
688
|
+
authors: [params.relayPublicKey],
|
|
689
|
+
"#d": params.channelIds,
|
|
690
|
+
limit: params.channelIds.length
|
|
691
|
+
}],
|
|
692
|
+
signal: params.signal,
|
|
693
|
+
timeoutMessage: "Timed out loading Buzz room membership snapshot",
|
|
694
|
+
abortMessage: "Buzz room membership query aborted",
|
|
695
|
+
failureMessage: "Buzz room membership query failed",
|
|
696
|
+
closeReason: MEMBERSHIP_QUERY_COMPLETE_REASON,
|
|
697
|
+
closeMessage: (reason) => `Buzz room membership query closed: ${reason}`,
|
|
698
|
+
onEvent: (event) => {
|
|
699
|
+
const membership = parseBuzzRoomMembershipEvent(event, params.relayPublicKey);
|
|
700
|
+
if (membership && configuredRooms.has(membership.roomId) && isNewerBuzzRoomMembership(membership, memberships.get(membership.roomId))) memberships.set(membership.roomId, membership);
|
|
701
|
+
},
|
|
702
|
+
result: () => memberships,
|
|
703
|
+
checkAbortAfterSubscribe: true
|
|
786
704
|
});
|
|
787
705
|
}
|
|
788
706
|
async function queryBuzzRoomMemberships(params) {
|
|
@@ -798,62 +716,35 @@ async function queryBuzzRoomMemberships(params) {
|
|
|
798
716
|
}
|
|
799
717
|
//#endregion
|
|
800
718
|
//#region extensions/buzz/src/history-catchup.ts
|
|
801
|
-
const HISTORY_PAGE_TIMEOUT_MS = 1e4;
|
|
802
719
|
const HISTORY_PAGE_COMPLETE_REASON = "buzz room history page loaded";
|
|
803
720
|
async function queryBuzzRoomHistoryPage(params) {
|
|
804
721
|
const events = [];
|
|
805
722
|
let overLimit = false;
|
|
806
|
-
return await
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
params.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
kinds: [...BUZZ_INBOUND_MESSAGE_KINDS],
|
|
832
|
-
"#h": [params.channelId],
|
|
833
|
-
since: params.since,
|
|
834
|
-
until: params.until,
|
|
835
|
-
...params.requestLimit === void 0 ? {} : { limit: params.requestLimit }
|
|
836
|
-
}], {
|
|
837
|
-
onevent: (event) => {
|
|
838
|
-
if (params.skipEventIds?.has(event.id)) return;
|
|
839
|
-
if (events.length < params.maxEvents) events.push(event);
|
|
840
|
-
else overLimit = true;
|
|
841
|
-
},
|
|
842
|
-
oneose: () => {
|
|
843
|
-
receivedEose = true;
|
|
844
|
-
if (settled) subscriptionRef.current?.close(HISTORY_PAGE_COMPLETE_REASON);
|
|
845
|
-
else finish();
|
|
846
|
-
},
|
|
847
|
-
onclose: (reason) => {
|
|
848
|
-
if (reason !== HISTORY_PAGE_COMPLETE_REASON) finish(/* @__PURE__ */ new Error(`Buzz room history query closed for ${params.channelId}: ${reason}`));
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
} catch (error) {
|
|
852
|
-
finish(error);
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
if (settled && receivedEose) subscriptionRef.current.close(HISTORY_PAGE_COMPLETE_REASON);
|
|
856
|
-
if (params.signal?.aborted) onAbort();
|
|
723
|
+
return await queryBuzzRelaySnapshot({
|
|
724
|
+
relay: params.relay,
|
|
725
|
+
filters: [{
|
|
726
|
+
kinds: [...BUZZ_INBOUND_MESSAGE_KINDS],
|
|
727
|
+
"#h": [params.channelId],
|
|
728
|
+
since: params.since,
|
|
729
|
+
until: params.until,
|
|
730
|
+
...params.requestLimit === void 0 ? {} : { limit: params.requestLimit }
|
|
731
|
+
}],
|
|
732
|
+
signal: params.signal,
|
|
733
|
+
timeoutMessage: `Timed out loading Buzz room history for ${params.channelId}`,
|
|
734
|
+
abortMessage: "Buzz room history query aborted",
|
|
735
|
+
failureMessage: "Buzz room history query failed",
|
|
736
|
+
closeReason: HISTORY_PAGE_COMPLETE_REASON,
|
|
737
|
+
closeMessage: (reason) => `Buzz room history query closed for ${params.channelId}: ${reason}`,
|
|
738
|
+
onEvent: (event) => {
|
|
739
|
+
if (params.skipEventIds?.has(event.id)) return;
|
|
740
|
+
if (events.length < params.maxEvents) events.push(event);
|
|
741
|
+
else overLimit = true;
|
|
742
|
+
},
|
|
743
|
+
result: () => ({
|
|
744
|
+
events,
|
|
745
|
+
overLimit
|
|
746
|
+
}),
|
|
747
|
+
checkAbortAfterSubscribe: true
|
|
857
748
|
});
|
|
858
749
|
}
|
|
859
750
|
async function drainBuzzRoomHistoryRange(params) {
|
|
@@ -1027,6 +918,14 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1027
918
|
params.onFatalError?.(error instanceof Error ? error : new Error(String(error)));
|
|
1028
919
|
params.relay.close();
|
|
1029
920
|
};
|
|
921
|
+
const replaceMembership = (membership) => {
|
|
922
|
+
if (membership.roles.get(params.botPublicKey) !== "bot" || !membership.members.has(params.botPublicKey)) {
|
|
923
|
+
blockedRooms.add(membership.roomId);
|
|
924
|
+
throw new Error(`Buzz bot no longer has the Bot role in room ${membership.roomId}`);
|
|
925
|
+
}
|
|
926
|
+
memberships.set(membership.roomId, membership);
|
|
927
|
+
params.onMembershipsChanged?.(effectiveMemberships());
|
|
928
|
+
};
|
|
1030
929
|
const queryMembership = (channelId) => {
|
|
1031
930
|
const query = membershipQueryTail.then(async () => (await queryBuzzRoomMemberships({
|
|
1032
931
|
relay: params.relay,
|
|
@@ -1038,8 +937,6 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1038
937
|
return query;
|
|
1039
938
|
};
|
|
1040
939
|
const refreshMembership = async (channelId, state) => {
|
|
1041
|
-
const baseline = memberships.get(channelId);
|
|
1042
|
-
if (!baseline) throw new Error(`Missing Buzz room membership for ${channelId}`);
|
|
1043
940
|
for (const delayMs of MEMBERSHIP_REFRESH_DELAYS_MS) {
|
|
1044
941
|
const generation = state.generation;
|
|
1045
942
|
state.lastAttemptedGeneration = generation;
|
|
@@ -1053,19 +950,14 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1053
950
|
continue;
|
|
1054
951
|
}
|
|
1055
952
|
if (state.generation !== generation || !refreshed) continue;
|
|
953
|
+
const current = memberships.get(channelId);
|
|
954
|
+
if (current && isNewerBuzzRoomMembership(current, refreshed)) refreshed = current;
|
|
1056
955
|
const pending = pendingMemberships.get(channelId);
|
|
1057
|
-
|
|
1058
|
-
const botMembershipChanged = pending?.has(params.botPublicKey) === true;
|
|
1059
|
-
if (!pendingMatches || botMembershipChanged && !isNewerBuzzRoomMembership(refreshed, baseline)) continue;
|
|
1060
|
-
if (refreshed.roles.get(params.botPublicKey) !== "bot" || !refreshed.members.has(params.botPublicKey)) {
|
|
1061
|
-
blockedRooms.add(channelId);
|
|
1062
|
-
throw new Error(`Buzz bot no longer has the Bot role in room ${channelId}`);
|
|
1063
|
-
}
|
|
1064
|
-
memberships.set(channelId, refreshed);
|
|
956
|
+
if (!(!pending || [...pending].every(([publicKey, expected]) => refreshed.members.has(publicKey) === (expected === "present")))) continue;
|
|
1065
957
|
pendingMemberships.delete(channelId);
|
|
1066
958
|
deniedMembers.delete(channelId);
|
|
1067
959
|
blockedRooms.delete(channelId);
|
|
1068
|
-
|
|
960
|
+
replaceMembership(refreshed);
|
|
1069
961
|
return;
|
|
1070
962
|
}
|
|
1071
963
|
if (state.generation !== state.lastAttemptedGeneration) return;
|
|
@@ -1116,6 +1008,15 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1116
1008
|
return refreshMembershipOnce(channelId);
|
|
1117
1009
|
};
|
|
1118
1010
|
const handleRoomEvent = (event, reservation) => {
|
|
1011
|
+
if (event.kind === 39002) {
|
|
1012
|
+
const membership = parseBuzzRoomMembershipEvent(event, params.relayPublicKey);
|
|
1013
|
+
if (membership && memberships.has(membership.roomId) && isNewerBuzzRoomMembership(membership, memberships.get(membership.roomId))) try {
|
|
1014
|
+
replaceMembership(membership);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
reportSystemEventError(error);
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1119
1020
|
if (isBuzzInboundMessageKind(event.kind)) {
|
|
1120
1021
|
params.onMessageEvent(event, isMember, reservation);
|
|
1121
1022
|
return;
|
|
@@ -1135,38 +1036,50 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1135
1036
|
}, MEMBERSHIP_READY_TIMEOUT_MS);
|
|
1136
1037
|
const subscriptions = [];
|
|
1137
1038
|
try {
|
|
1138
|
-
for (const channelId of params.channelIds)
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
oldest: event.created_at
|
|
1157
|
-
});
|
|
1039
|
+
for (const channelId of params.channelIds) {
|
|
1040
|
+
const filters = [
|
|
1041
|
+
{
|
|
1042
|
+
kinds: [BUZZ_ROOM_SYSTEM_KIND, BUZZ_ROOM_METADATA_EDIT_KIND],
|
|
1043
|
+
"#h": [channelId],
|
|
1044
|
+
since: params.since
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
kinds: [BUZZ_ROOM_MEMBERSHIP_KIND],
|
|
1048
|
+
authors: [params.relayPublicKey],
|
|
1049
|
+
"#d": [channelId],
|
|
1050
|
+
limit: 1
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
kinds: [...BUZZ_INBOUND_MESSAGE_KINDS],
|
|
1054
|
+
"#h": [channelId],
|
|
1055
|
+
since: params.messageSince(channelId),
|
|
1056
|
+
limit: params.messageLimit
|
|
1158
1057
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1058
|
+
];
|
|
1059
|
+
subscriptions.push(openBuzzRelaySubscription(params.relay, filters, {
|
|
1060
|
+
onevent: (event) => {
|
|
1061
|
+
if (!historicalRooms.has(channelId) && isBuzzInboundMessageKind(event.kind)) {
|
|
1062
|
+
const page = historyPages.get(channelId);
|
|
1063
|
+
if (page) {
|
|
1064
|
+
page.count += 1;
|
|
1065
|
+
page.oldest = Math.min(page.oldest, event.created_at);
|
|
1066
|
+
} else historyPages.set(channelId, {
|
|
1067
|
+
count: 1,
|
|
1068
|
+
oldest: event.created_at
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
handleRoomEvent(event);
|
|
1072
|
+
},
|
|
1073
|
+
oneose: () => {
|
|
1074
|
+
historicalRooms.add(channelId);
|
|
1075
|
+
if (historicalRooms.size === params.channelIds.length) resolveHistorical?.();
|
|
1076
|
+
},
|
|
1077
|
+
onclose: (reason) => {
|
|
1078
|
+
if (!historicalRooms.has(channelId)) rejectHistorical?.(/* @__PURE__ */ new Error(`Buzz membership subscription closed for ${channelId}: ${reason}`));
|
|
1079
|
+
else if (reason !== "shutdown" && reason !== "relay connection closed by us" && reason !== MEMBERSHIP_TRACKER_SETUP_CLOSE_REASON && !params.signal?.aborted) params.onFatalError?.(/* @__PURE__ */ new Error(`Buzz membership subscription closed for ${channelId}: ${reason}`));
|
|
1080
|
+
}
|
|
1081
|
+
}, filters.map((filter) => Object.assign({}, filter, { "#h": [channelId] }))));
|
|
1082
|
+
}
|
|
1170
1083
|
await historicalReady;
|
|
1171
1084
|
} catch (error) {
|
|
1172
1085
|
if (params.relay.connected) {
|
|
@@ -1187,7 +1100,7 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1187
1100
|
if (await catchUpBuzzRoomHistory({
|
|
1188
1101
|
relay: params.relay,
|
|
1189
1102
|
channelId,
|
|
1190
|
-
since: params.messageSince,
|
|
1103
|
+
since: params.messageSince(channelId),
|
|
1191
1104
|
until: page.oldest,
|
|
1192
1105
|
limit: params.messageLimit,
|
|
1193
1106
|
reserveCapacity: params.reserveDispatchCapacity,
|
|
@@ -1208,10 +1121,11 @@ async function createBuzzRoomMembershipTracker(params) {
|
|
|
1208
1121
|
const BUZZ_RELAY_MAX_SUBSCRIPTIONS = 1024;
|
|
1209
1122
|
const BUZZ_RELAY_NON_ROOM_PROFILE_SUBSCRIPTION_RESERVE = 4;
|
|
1210
1123
|
const BUZZ_DIRECTORY_MAX_PROFILE_SUBSCRIPTIONS = 10;
|
|
1124
|
+
const BUZZ_MAX_CONFIGURED_ROOMS = BUZZ_RELAY_MAX_SUBSCRIPTIONS - BUZZ_RELAY_NON_ROOM_PROFILE_SUBSCRIPTION_RESERVE;
|
|
1211
1125
|
function resolveBuzzSubscriptionBudget(roomCount) {
|
|
1212
1126
|
if (!Number.isSafeInteger(roomCount) || roomCount < 0) throw new Error("Buzz configured room count must be a non-negative integer");
|
|
1213
1127
|
const availableProfileSubscriptions = BUZZ_RELAY_MAX_SUBSCRIPTIONS - BUZZ_RELAY_NON_ROOM_PROFILE_SUBSCRIPTION_RESERVE - roomCount;
|
|
1214
|
-
if (availableProfileSubscriptions < 0) throw new Error(`Buzz supports at most ${
|
|
1128
|
+
if (availableProfileSubscriptions < 0) throw new Error(`Buzz supports at most ${BUZZ_MAX_CONFIGURED_ROOMS} configured rooms per account`);
|
|
1215
1129
|
return { profileLimit: Math.min(BUZZ_DIRECTORY_MAX_PROFILE_SUBSCRIPTIONS, availableProfileSubscriptions) * 200 };
|
|
1216
1130
|
}
|
|
1217
1131
|
//#endregion
|
|
@@ -1257,9 +1171,11 @@ function startBuzzPresenceHeartbeat(params) {
|
|
|
1257
1171
|
await params.relay.publish(buildBuzzPresenceEvent(params.secretKey));
|
|
1258
1172
|
errorReported = false;
|
|
1259
1173
|
} catch (error) {
|
|
1260
|
-
|
|
1174
|
+
const failure = error instanceof Error ? error : new Error("Buzz presence heartbeat failed", { cause: error });
|
|
1175
|
+
if (!stopped && failure.message === "publish timed out") params.onFatalError(failure);
|
|
1176
|
+
else if (!stopped && !errorReported) {
|
|
1261
1177
|
errorReported = true;
|
|
1262
|
-
params.onError?.(
|
|
1178
|
+
params.onError?.(failure);
|
|
1263
1179
|
}
|
|
1264
1180
|
} finally {
|
|
1265
1181
|
publishInFlight = false;
|
|
@@ -1345,10 +1261,9 @@ async function startBuzzBus(options) {
|
|
|
1345
1261
|
const sessionStartedAt = Math.floor(Date.now() / 1e3);
|
|
1346
1262
|
const lifecycleAbort = new AbortController();
|
|
1347
1263
|
const signal = options.signal ? AbortSignal.any([options.signal, lifecycleAbort.signal]) : lifecycleAbort.signal;
|
|
1348
|
-
let fatalErrorReported = false;
|
|
1349
1264
|
const reportFatalError = (error) => {
|
|
1350
|
-
if (signal.aborted
|
|
1351
|
-
|
|
1265
|
+
if (signal.aborted) return;
|
|
1266
|
+
lifecycleAbort.abort(error);
|
|
1352
1267
|
options.onFatalError?.(error);
|
|
1353
1268
|
};
|
|
1354
1269
|
const replayGuard = createChannelReplayGuard({
|
|
@@ -1458,7 +1373,7 @@ async function startBuzzBus(options) {
|
|
|
1458
1373
|
channelIds: activeChannelIds,
|
|
1459
1374
|
botPublicKey: publicKey,
|
|
1460
1375
|
since: sessionStartedAt,
|
|
1461
|
-
messageSince: options.since ?? sessionStartedAt,
|
|
1376
|
+
messageSince: (channelId) => options.since?.(channelId) ?? sessionStartedAt,
|
|
1462
1377
|
messageLimit: resolveBuzzRoomHistoryLimit(activeChannelIds.length),
|
|
1463
1378
|
reserveDispatchCapacity: (slots) => dispatchQueue.reserveCapacity(slots),
|
|
1464
1379
|
onHistoryError: options.onHistoryError,
|
|
@@ -1468,7 +1383,12 @@ async function startBuzzBus(options) {
|
|
|
1468
1383
|
if (!message || !isMember(message.channelId, event.pubkey)) return;
|
|
1469
1384
|
if ((reservation ?? dispatchQueue).enqueue(async () => {
|
|
1470
1385
|
await replayGuard.processGuarded(event, async () => {
|
|
1471
|
-
|
|
1386
|
+
const assertCurrent = () => {
|
|
1387
|
+
signal.throwIfAborted();
|
|
1388
|
+
if (!isMember(message.channelId, event.pubkey)) throw new Error("Buzz sender is no longer a room member");
|
|
1389
|
+
};
|
|
1390
|
+
assertCurrent();
|
|
1391
|
+
await options.onMessage(message, bus, signal, assertCurrent);
|
|
1472
1392
|
});
|
|
1473
1393
|
}) !== "overflow") return;
|
|
1474
1394
|
if (reservation) {
|
|
@@ -1495,7 +1415,8 @@ async function startBuzzBus(options) {
|
|
|
1495
1415
|
stopPresenceHeartbeat = startBuzzPresenceHeartbeat({
|
|
1496
1416
|
relay,
|
|
1497
1417
|
secretKey,
|
|
1498
|
-
onError: options.onPresenceError
|
|
1418
|
+
onError: options.onPresenceError,
|
|
1419
|
+
onFatalError: reportFatalError
|
|
1499
1420
|
});
|
|
1500
1421
|
if (options.profileName?.trim()) syncBuzzProfile({
|
|
1501
1422
|
relay,
|
|
@@ -1573,8 +1494,8 @@ async function handleBuzzInbound(params) {
|
|
|
1573
1494
|
canDetectMention: true,
|
|
1574
1495
|
wasMentioned
|
|
1575
1496
|
},
|
|
1576
|
-
groupPolicy: account.config.groupPolicy,
|
|
1577
|
-
groupAllowFrom: account.config.groupAllowFrom,
|
|
1497
|
+
groupPolicy: groupConfig?.groupPolicy ?? account.config.groupPolicy,
|
|
1498
|
+
groupAllowFrom: groupConfig?.groupAllowFrom ?? account.config.groupAllowFrom,
|
|
1578
1499
|
policy: { activation: {
|
|
1579
1500
|
requireMention: groupConfig?.requireMention ?? true,
|
|
1580
1501
|
allowTextCommands: true
|
|
@@ -1584,6 +1505,7 @@ async function handleBuzzInbound(params) {
|
|
|
1584
1505
|
hasControlCommand
|
|
1585
1506
|
} : void 0
|
|
1586
1507
|
});
|
|
1508
|
+
params.assertCurrent();
|
|
1587
1509
|
if (access.ingress.admission !== "dispatch") return;
|
|
1588
1510
|
const senderName = bus.directory.resolveSenderName(message.senderPubkey);
|
|
1589
1511
|
const roomName = bus.directory.resolveRoomName(channelId);
|
|
@@ -1643,6 +1565,11 @@ async function handleBuzzInbound(params) {
|
|
|
1643
1565
|
BuzzEventKind: message.kind
|
|
1644
1566
|
}
|
|
1645
1567
|
});
|
|
1568
|
+
const replyTarget = {
|
|
1569
|
+
channelId,
|
|
1570
|
+
threadId: message.threadId,
|
|
1571
|
+
replyToId: message.threadId ?? message.id
|
|
1572
|
+
};
|
|
1646
1573
|
await runtime.channel.inbound.dispatch({
|
|
1647
1574
|
cfg,
|
|
1648
1575
|
channel: "buzz",
|
|
@@ -1658,10 +1585,8 @@ async function handleBuzzInbound(params) {
|
|
|
1658
1585
|
const text = payload && typeof payload === "object" && "text" in payload ? payload.text ?? "" : "";
|
|
1659
1586
|
if (!text.trim()) return;
|
|
1660
1587
|
await bus.sendText({
|
|
1661
|
-
|
|
1662
|
-
text
|
|
1663
|
-
threadId: message.threadId,
|
|
1664
|
-
replyToId: message.id
|
|
1588
|
+
...replyTarget,
|
|
1589
|
+
text
|
|
1665
1590
|
});
|
|
1666
1591
|
},
|
|
1667
1592
|
onError: (error) => {
|
|
@@ -1671,11 +1596,7 @@ async function handleBuzzInbound(params) {
|
|
|
1671
1596
|
replyOptions: { abortSignal: signal },
|
|
1672
1597
|
replyPipeline: { typing: {
|
|
1673
1598
|
start: async () => {
|
|
1674
|
-
await bus.sendTyping(
|
|
1675
|
-
channelId,
|
|
1676
|
-
threadId: message.threadId,
|
|
1677
|
-
replyToId: message.id
|
|
1678
|
-
});
|
|
1599
|
+
await bus.sendTyping(replyTarget);
|
|
1679
1600
|
},
|
|
1680
1601
|
keepaliveIntervalMs: 3e3,
|
|
1681
1602
|
onStartError: (error) => {
|
|
@@ -1688,6 +1609,38 @@ async function handleBuzzInbound(params) {
|
|
|
1688
1609
|
});
|
|
1689
1610
|
}
|
|
1690
1611
|
//#endregion
|
|
1612
|
+
//#region extensions/buzz/src/recovery-watermark.ts
|
|
1613
|
+
function roomCursorKey(channelId) {
|
|
1614
|
+
return `room:${channelId}`;
|
|
1615
|
+
}
|
|
1616
|
+
function openBuzzRecoveryWatermarkStore(params) {
|
|
1617
|
+
const accountScope = createHash("sha256").update(params.accountId).digest("hex").slice(0, 16);
|
|
1618
|
+
return getBuzzRuntime().state.openKeyedStore({
|
|
1619
|
+
namespace: `buzz.recovery-watermark-${accountScope}`,
|
|
1620
|
+
maxEntries: BUZZ_MAX_CONFIGURED_ROOMS,
|
|
1621
|
+
overflowPolicy: "reject-new"
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
async function resolveBuzzRecoverySince(params) {
|
|
1625
|
+
const { store, channelIds, nowSeconds, lookbackSeconds } = params;
|
|
1626
|
+
const configuredKeys = new Set(channelIds.map(roomCursorKey));
|
|
1627
|
+
for (const { key } of await store.entries()) if (key.startsWith("room:") && !configuredKeys.has(key)) await store.delete(key);
|
|
1628
|
+
const sinceByRoom = /* @__PURE__ */ new Map();
|
|
1629
|
+
const retentionFloor = nowSeconds - lookbackSeconds;
|
|
1630
|
+
for (const channelId of channelIds) {
|
|
1631
|
+
const key = roomCursorKey(channelId);
|
|
1632
|
+
const activation = await store.lookup(key);
|
|
1633
|
+
if (activation === void 0) {
|
|
1634
|
+
await store.register(key, { seconds: nowSeconds });
|
|
1635
|
+
sinceByRoom.set(channelId, nowSeconds);
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (typeof activation.seconds !== "number" || !Number.isFinite(activation.seconds)) throw new Error(`Invalid Buzz recovery watermark for room ${channelId}`);
|
|
1639
|
+
sinceByRoom.set(channelId, Math.min(Math.max(activation.seconds, retentionFloor), nowSeconds));
|
|
1640
|
+
}
|
|
1641
|
+
return sinceByRoom;
|
|
1642
|
+
}
|
|
1643
|
+
//#endregion
|
|
1691
1644
|
//#region extensions/buzz/src/gateway.ts
|
|
1692
1645
|
const activeBuses = /* @__PURE__ */ new Map();
|
|
1693
1646
|
const RECONNECT_BACKOFF = {
|
|
@@ -1720,10 +1673,8 @@ function resolveBuzzProfileName(params) {
|
|
|
1720
1673
|
}
|
|
1721
1674
|
async function startBuzzGatewayAccount(ctx) {
|
|
1722
1675
|
const buildContext = ctx.channelRuntime?.inbound.buildContext;
|
|
1723
|
-
const account =
|
|
1724
|
-
|
|
1725
|
-
accountId: ctx.account.accountId
|
|
1726
|
-
});
|
|
1676
|
+
const account = ctx.account;
|
|
1677
|
+
assertBuzzAccountAvailable(account);
|
|
1727
1678
|
if (!account.configured) throw new Error(`Buzz is not configured for account "${account.accountId}"`);
|
|
1728
1679
|
const channelIds = Object.entries(account.config.groups ?? {}).filter(([, config]) => config.enabled !== false).map(([channelId]) => parseBuzzTarget(channelId));
|
|
1729
1680
|
if (channelIds.length === 0) throw new Error("Buzz requires at least one channels.buzz.groups entry");
|
|
@@ -1733,7 +1684,7 @@ async function startBuzzGatewayAccount(ctx) {
|
|
|
1733
1684
|
account,
|
|
1734
1685
|
channelIds
|
|
1735
1686
|
});
|
|
1736
|
-
|
|
1687
|
+
const watermarkStore = openBuzzRecoveryWatermarkStore({ accountId: account.accountId });
|
|
1737
1688
|
let reconnectAttempt = 0;
|
|
1738
1689
|
while (!ctx.abortSignal.aborted) {
|
|
1739
1690
|
let bus;
|
|
@@ -1744,8 +1695,13 @@ async function startBuzzGatewayAccount(ctx) {
|
|
|
1744
1695
|
reportBusFailure = resolve;
|
|
1745
1696
|
});
|
|
1746
1697
|
try {
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1698
|
+
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
1699
|
+
const sinceByRoom = await resolveBuzzRecoverySince({
|
|
1700
|
+
store: watermarkStore,
|
|
1701
|
+
channelIds,
|
|
1702
|
+
nowSeconds,
|
|
1703
|
+
lookbackSeconds: RECONNECT_LOOKBACK_SECONDS
|
|
1704
|
+
});
|
|
1749
1705
|
bus = await startBuzzBus({
|
|
1750
1706
|
accountId: account.accountId,
|
|
1751
1707
|
relayUrl: account.relayUrl,
|
|
@@ -1753,9 +1709,9 @@ async function startBuzzGatewayAccount(ctx) {
|
|
|
1753
1709
|
authTag: account.authTag,
|
|
1754
1710
|
profileName,
|
|
1755
1711
|
channelIds,
|
|
1756
|
-
since:
|
|
1712
|
+
since: (channelId) => sinceByRoom.get(channelId) ?? nowSeconds,
|
|
1757
1713
|
signal: ctx.abortSignal,
|
|
1758
|
-
onMessage: async (message, sessionBus, signal) => {
|
|
1714
|
+
onMessage: async (message, sessionBus, signal, assertCurrent) => {
|
|
1759
1715
|
if (!isConfiguredBuzzChannel(configuredChannelIds, message.channelId)) return;
|
|
1760
1716
|
await handleBuzzInbound({
|
|
1761
1717
|
account,
|
|
@@ -1763,6 +1719,7 @@ async function startBuzzGatewayAccount(ctx) {
|
|
|
1763
1719
|
bus: sessionBus,
|
|
1764
1720
|
message,
|
|
1765
1721
|
signal,
|
|
1722
|
+
assertCurrent,
|
|
1766
1723
|
buildContext
|
|
1767
1724
|
});
|
|
1768
1725
|
},
|
|
@@ -1810,8 +1767,8 @@ async function startBuzzGatewayAccount(ctx) {
|
|
|
1810
1767
|
if (ctx.abortSignal.aborted) return;
|
|
1811
1768
|
cycleError = error instanceof Error ? error : new Error(String(error));
|
|
1812
1769
|
} finally {
|
|
1813
|
-
await bus?.close();
|
|
1814
1770
|
if (activeBuses.get(account.accountId) === bus) activeBuses.delete(account.accountId);
|
|
1771
|
+
await bus?.close();
|
|
1815
1772
|
ctx.setStatus({
|
|
1816
1773
|
accountId: account.accountId,
|
|
1817
1774
|
running: false,
|
|
@@ -1847,6 +1804,7 @@ const buzzOutboundAdapter = {
|
|
|
1847
1804
|
cfg,
|
|
1848
1805
|
accountId: resolvedAccountId
|
|
1849
1806
|
});
|
|
1807
|
+
assertBuzzAccountAvailable(account);
|
|
1850
1808
|
if (!account.enabled) throw new Error(`Buzz is disabled for account ${resolvedAccountId}`);
|
|
1851
1809
|
if (!account.configured) throw new Error(`Buzz is not configured for account ${resolvedAccountId}`);
|
|
1852
1810
|
const bus = activeBuses.get(resolvedAccountId);
|
|
@@ -1893,6 +1851,7 @@ function createConfiguredDirectoryState(params) {
|
|
|
1893
1851
|
cfg: params.cfg,
|
|
1894
1852
|
accountId: params.accountId
|
|
1895
1853
|
});
|
|
1854
|
+
assertBuzzAccountAvailable(account);
|
|
1896
1855
|
if (!account.publicKey) return null;
|
|
1897
1856
|
const channelIds = resolveConfiguredRoomIds(account);
|
|
1898
1857
|
return {
|
|
@@ -2010,7 +1969,8 @@ const buzzPlugin = createChatChannelPlugin({
|
|
|
2010
1969
|
configured: account.configured,
|
|
2011
1970
|
extra: {
|
|
2012
1971
|
baseUrl: account.relayUrl,
|
|
2013
|
-
publicKey: account.publicKey
|
|
1972
|
+
publicKey: account.publicKey,
|
|
1973
|
+
tokenStatus: account.tokenStatus
|
|
2014
1974
|
}
|
|
2015
1975
|
}),
|
|
2016
1976
|
resolveAllowFrom: ({ cfg, accountId }) => resolveBuzzAccount({
|
|
@@ -2081,11 +2041,15 @@ const buzzPlugin = createChatChannelPlugin({
|
|
|
2081
2041
|
name: account.name,
|
|
2082
2042
|
enabled: account.enabled,
|
|
2083
2043
|
configured: account.configured,
|
|
2084
|
-
|
|
2085
|
-
|
|
2044
|
+
extra: {
|
|
2045
|
+
baseUrl: account.relayUrl,
|
|
2046
|
+
publicKey: account.publicKey,
|
|
2047
|
+
tokenStatus: account.tokenStatus
|
|
2048
|
+
}
|
|
2086
2049
|
})
|
|
2087
2050
|
}),
|
|
2088
2051
|
probeAccount: async ({ account, timeoutMs }) => {
|
|
2052
|
+
assertBuzzAccountAvailable(account);
|
|
2089
2053
|
const rooms = await discoverBuzzRooms({
|
|
2090
2054
|
relayUrl: account.relayUrl,
|
|
2091
2055
|
privateKey: account.privateKey,
|