@openclaw/buzz 2026.9.6 → 2026.9.7

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/assets/icon.png CHANGED
Binary file
@@ -1,5 +1,5 @@
1
- import { m as parseBuzzTarget, n as decodeBuzzPrivateKey, o as resolveBuzzPublicKey, u as buildBuzzTarget } from "./types-CeupBG0P.mjs";
2
- import { c as openBuzzRelaySubscription, i as parseBuzzRoomMembershipEvent, o as connectAuthenticatedBuzzRelaySession, s as parseBuzzAuthTag, t as BUZZ_ROOM_MEMBERSHIP_KIND, u as isNewerBuzzRevision } from "./room-membership-tqLQznFE.mjs";
1
+ import { m as parseBuzzTarget, n as decodeBuzzPrivateKey, o as resolveBuzzPublicKey, u as buildBuzzTarget } from "./types-DRUZMyTx.mjs";
2
+ import { c as openBuzzRelaySubscription, i as parseBuzzRoomMembershipEvent, m as isNewerBuzzRevision, o as connectAuthenticatedBuzzRelaySession, s as parseBuzzAuthTag, t as BUZZ_ROOM_MEMBERSHIP_KIND } from "./room-membership-7of91SyC.mjs";
3
3
  import { i as buildBuzzMessageTags, s as parseBuzzMessageEvent } from "./message-event-X_aBiM1X.mjs";
4
4
  import { z } from "zod";
5
5
  import { finalizeEvent } from "nostr-tools";
@@ -1,18 +1,18 @@
1
- import { a as resolveBuzzAccountConfig, c as BuzzConfigSchema, d as isConfiguredBuzzChannel, f as looksLikeBuzzTarget, i as resolveBuzzAccount, m as parseBuzzTarget, n as decodeBuzzPrivateKey, o as resolveBuzzPublicKey, p as normalizeBuzzTarget, r as listBuzzAccountIds, s as resolveDefaultBuzzAccountId, t as assertBuzzAccountAvailable, u as buildBuzzTarget } from "./types-CeupBG0P.mjs";
2
- import { n as listBuzzDirectoryPeersFromConfig, r as applyBuzzDirectoryQueryAndLimit, t as listBuzzDirectoryGroupsFromConfig } from "./directory-config-CzHz6fzo.mjs";
3
- import { a as connectAuthenticatedBuzzRelay, c as openBuzzRelaySubscription, i as parseBuzzRoomMembershipEvent, l as queryBuzzRelaySnapshot, n as BUZZ_ROOM_SYSTEM_KIND, o as connectAuthenticatedBuzzRelaySession, r as parseBuzzRoomMembershipChangeEvent, s as parseBuzzAuthTag, t as BUZZ_ROOM_MEMBERSHIP_KIND, u as isNewerBuzzRevision } from "./room-membership-tqLQznFE.mjs";
1
+ import { a as resolveBuzzAccountConfig, c as BuzzConfigSchema, d as isConfiguredBuzzChannel, f as looksLikeBuzzTarget, i as resolveBuzzAccount, m as parseBuzzTarget, n as decodeBuzzPrivateKey, o as resolveBuzzPublicKey, p as normalizeBuzzTarget, r as listBuzzAccountIds, s as resolveDefaultBuzzAccountId, t as assertBuzzAccountAvailable, u as buildBuzzTarget } from "./types-DRUZMyTx.mjs";
2
+ import { n as listBuzzDirectoryPeersFromConfig, t as listBuzzDirectoryGroupsFromConfig } from "./directory-config-M9KSexdh.mjs";
3
+ import { a as connectAuthenticatedBuzzRelay, c as openBuzzRelaySubscription, d as resolveBuzzSubscriptionBudget, f as BUZZ_ROOM_METADATA_KIND, i as parseBuzzRoomMembershipEvent, l as queryBuzzRelaySnapshot, m as isNewerBuzzRevision, n as BUZZ_ROOM_SYSTEM_KIND, o as connectAuthenticatedBuzzRelaySession, p as BuzzDirectoryState, r as parseBuzzRoomMembershipChangeEvent, s as parseBuzzAuthTag, t as BUZZ_ROOM_MEMBERSHIP_KIND, u as BUZZ_MAX_CONFIGURED_ROOMS } from "./room-membership-7of91SyC.mjs";
4
4
  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-X_aBiM1X.mjs";
5
- import { i as startBuzzRoomMembershipNotifications, n as buzzSetupContract, r as discoverBuzzRooms, t as buzzSetupWizard } from "./setup-surface-DOHxdPor.mjs";
6
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-DjibkTDL.mjs";
5
+ import { i as startBuzzRoomMembershipNotifications, n as buzzSetupContract, r as discoverBuzzRooms, t as buzzSetupWizard } from "./setup-surface-CVXY6_50.mjs";
6
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-B2-Mrm4v.mjs";
7
7
  import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
8
8
  import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
9
9
  import { createChannelMessageAdapterFromOutbound, waitUntilAbort } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
11
11
  import { parseThreadSessionSuffix } from "openclaw/plugin-sdk/routing";
12
12
  import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
13
- import { compareEvents, finalizeEvent } from "nostr-tools";
13
+ import { compareEvents, finalizeEvent, verifyEvent } from "nostr-tools";
14
14
  import { chunkItems } from "openclaw/plugin-sdk/text-chunking";
15
- import { truncateUtf16Safe, truncateUtf8Prefix } from "openclaw/plugin-sdk/text-utility-runtime";
15
+ import { truncateUtf8Prefix } from "openclaw/plugin-sdk/text-utility-runtime";
16
16
  import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
17
17
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
18
18
  import { computeBackoff, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
@@ -23,218 +23,6 @@ import { createSubsystemLogger } from "openclaw/plugin-sdk/logging-core";
23
23
  import { buildHistoryContext, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
24
24
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
25
25
  import { createHash } from "node:crypto";
26
- const BUZZ_ROOM_METADATA_KIND = 39e3;
27
- const DEFAULT_BUZZ_DIRECTORY_PROFILE_LIMIT = 2e3;
28
- const HEX_PUBLIC_KEY_PATTERN = /^[0-9a-f]{64}$/u;
29
- const MAX_DIRECTORY_NAME_CHARS = 512;
30
- const MAX_DIRECTORY_HANDLE_CHARS = 320;
31
- const MAX_DIRECTORY_URL_CHARS = 4096;
32
- function normalizeBoundedString(value, maxChars) {
33
- if (typeof value !== "string") return;
34
- const trimmed = value.trim();
35
- if (!trimmed) return;
36
- return truncateUtf16Safe(trimmed, maxChars);
37
- }
38
- function readPreferredString(params) {
39
- if (Object.hasOwn(params.content, params.primary)) return normalizeBoundedString(params.content[params.primary], params.maxChars);
40
- return normalizeBoundedString(params.content[params.fallback], params.maxChars);
41
- }
42
- function fallbackPublicKeyLabel(publicKey) {
43
- return `${publicKey.slice(0, 8)}...${publicKey.slice(-6)}`;
44
- }
45
- function parseBuzzDirectoryProfileEvent(event) {
46
- const publicKey = event.pubkey.trim().toLowerCase();
47
- if (event.kind !== 0 || !HEX_PUBLIC_KEY_PATTERN.test(publicKey)) return;
48
- let content;
49
- try {
50
- const parsed = JSON.parse(event.content);
51
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
52
- content = parsed;
53
- } catch {
54
- return;
55
- }
56
- return {
57
- publicKey,
58
- displayName: readPreferredString({
59
- content,
60
- primary: "display_name",
61
- fallback: "name",
62
- maxChars: MAX_DIRECTORY_NAME_CHARS
63
- }),
64
- handle: normalizeBoundedString(content.nip05, MAX_DIRECTORY_HANDLE_CHARS),
65
- avatarUrl: readPreferredString({
66
- content,
67
- primary: "picture",
68
- fallback: "image",
69
- maxChars: MAX_DIRECTORY_URL_CHARS
70
- }),
71
- createdAt: event.created_at,
72
- eventId: event.id
73
- };
74
- }
75
- function parseBuzzDirectoryRoomEvent(event) {
76
- if (event.kind !== 39e3) return;
77
- const roomId = event.tags.find((tag) => tag[0] === "d")?.[1]?.trim().toLowerCase();
78
- if (!roomId) return;
79
- try {
80
- parseBuzzTarget(roomId);
81
- } catch {
82
- return;
83
- }
84
- return {
85
- roomId,
86
- name: normalizeBoundedString(event.tags.find((tag) => tag[0] === "name")?.[1], MAX_DIRECTORY_NAME_CHARS),
87
- archived: event.tags.some((tag) => tag[0] === "archived" && tag[1] === "true"),
88
- createdAt: event.created_at,
89
- eventId: event.id
90
- };
91
- }
92
- var BuzzDirectoryState = class {
93
- #publicKey;
94
- #fallbackProfileName;
95
- #configuredRoomIds;
96
- #profileLimit;
97
- #memberships = /* @__PURE__ */ new Map();
98
- #profilePublicKeys = /* @__PURE__ */ new Set();
99
- #profiles = /* @__PURE__ */ new Map();
100
- #rooms = /* @__PURE__ */ new Map();
101
- constructor(params) {
102
- this.#publicKey = params.publicKey.trim().toLowerCase();
103
- this.#fallbackProfileName = params.fallbackProfileName.trim() || "OpenClaw";
104
- this.#configuredRoomIds = new Set(params.channelIds.map(parseBuzzTarget));
105
- const requestedProfileLimit = params.profileLimit ?? DEFAULT_BUZZ_DIRECTORY_PROFILE_LIMIT;
106
- this.#profileLimit = Number.isFinite(requestedProfileLimit) && requestedProfileLimit >= 0 ? Math.floor(requestedProfileLimit) : DEFAULT_BUZZ_DIRECTORY_PROFILE_LIMIT;
107
- if (this.#profileLimit > 0) this.#profilePublicKeys.add(this.#publicKey);
108
- }
109
- replaceMemberships(memberships) {
110
- const nextMemberships = /* @__PURE__ */ new Map();
111
- const memberPublicKeys = /* @__PURE__ */ new Set();
112
- for (const roomId of this.#configuredRoomIds) {
113
- const membership = memberships.get(roomId);
114
- if (!membership) continue;
115
- nextMemberships.set(roomId, membership);
116
- for (const publicKey of membership.members) memberPublicKeys.add(publicKey);
117
- }
118
- memberPublicKeys.delete(this.#publicKey);
119
- const nextProfilePublicKeys = this.#profileLimit === 0 ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([this.#publicKey, ...[...memberPublicKeys].toSorted().slice(0, this.#profileLimit - 1)]);
120
- const profileSelectionChanged = nextProfilePublicKeys.size !== this.#profilePublicKeys.size || [...nextProfilePublicKeys].some((publicKey) => !this.#profilePublicKeys.has(publicKey));
121
- this.#memberships = nextMemberships;
122
- this.#profilePublicKeys = nextProfilePublicKeys;
123
- for (const publicKey of this.#profiles.keys()) if (!nextProfilePublicKeys.has(publicKey)) this.#profiles.delete(publicKey);
124
- return profileSelectionChanged;
125
- }
126
- profilePublicKeys() {
127
- return [...this.#profilePublicKeys];
128
- }
129
- activeRoomIds() {
130
- return [...this.#configuredRoomIds].filter((roomId) => !this.#rooms.get(roomId)?.archived);
131
- }
132
- isRoomArchived(roomId) {
133
- return this.#rooms.get(parseBuzzTarget(roomId))?.archived === true;
134
- }
135
- isBotMember(roomId, publicKey) {
136
- return this.isMember(roomId, publicKey) && this.#memberships.get(parseBuzzTarget(roomId))?.roles.get(publicKey) === "bot";
137
- }
138
- isMember(roomId, publicKey) {
139
- const normalizedRoomId = parseBuzzTarget(roomId);
140
- return !this.#rooms.get(normalizedRoomId)?.archived && this.#memberships.get(normalizedRoomId)?.members.has(publicKey) === true;
141
- }
142
- applyProfileEvent(event) {
143
- const profile = parseBuzzDirectoryProfileEvent(event);
144
- if (!profile || !this.#profilePublicKeys.has(profile.publicKey) || !isNewerBuzzRevision(profile, this.#profiles.get(profile.publicKey))) return false;
145
- this.#profiles.set(profile.publicKey, profile);
146
- return true;
147
- }
148
- applyRoomEvent(event) {
149
- const room = parseBuzzDirectoryRoomEvent(event);
150
- if (!room || !this.#configuredRoomIds.has(room.roomId) || !isNewerBuzzRevision(room, this.#rooms.get(room.roomId))) return false;
151
- this.#rooms.set(room.roomId, room);
152
- return true;
153
- }
154
- resolveSenderName(publicKey) {
155
- const normalized = publicKey.trim().toLowerCase();
156
- return this.#profiles.get(normalized)?.displayName ?? fallbackPublicKeyLabel(normalized);
157
- }
158
- resolveRoomName(roomId) {
159
- const normalized = parseBuzzTarget(roomId);
160
- return this.#rooms.get(normalized)?.name ?? normalized;
161
- }
162
- self() {
163
- return this.#buildUserEntry(this.#publicKey);
164
- }
165
- listPeers(params) {
166
- const peers = /* @__PURE__ */ new Set();
167
- for (const roomId of this.activeRoomIds()) {
168
- const membership = this.#memberships.get(roomId);
169
- if (!membership) continue;
170
- for (const publicKey of membership.members) if (publicKey !== this.#publicKey) peers.add(publicKey);
171
- }
172
- const entries = [...peers].map((publicKey) => this.#buildUserEntry(publicKey)).toSorted(compareDirectoryEntries);
173
- return applyBuzzDirectoryQueryAndLimit(entries, params);
174
- }
175
- listGroups(params) {
176
- const entries = this.activeRoomIds().map((roomId) => this.#buildRoomEntry(roomId)).toSorted(compareDirectoryEntries);
177
- return applyBuzzDirectoryQueryAndLimit(entries, params);
178
- }
179
- listGroupMembers(params) {
180
- let roomId;
181
- try {
182
- roomId = parseBuzzTarget(params.groupId);
183
- } catch {
184
- return [];
185
- }
186
- if (this.#rooms.get(roomId)?.archived) return [];
187
- const membership = this.#memberships.get(roomId);
188
- if (!membership) return [];
189
- const entries = [...membership.members].map((publicKey) => {
190
- const entry = this.#buildUserEntry(publicKey);
191
- entry.raw = {
192
- publicKey,
193
- role: membership.roles.get(publicKey),
194
- roomId
195
- };
196
- return entry;
197
- }).toSorted(compareDirectoryEntries);
198
- return applyBuzzDirectoryQueryAndLimit(entries, { limit: params.limit });
199
- }
200
- mentionMembers(roomId) {
201
- const normalized = parseBuzzTarget(roomId);
202
- if (this.#rooms.get(normalized)?.archived) return;
203
- const membership = this.#memberships.get(normalized);
204
- if (!membership) return;
205
- return [...membership.members].map((publicKey) => ({
206
- publicKey,
207
- displayName: this.#profiles.get(publicKey)?.displayName
208
- })).toSorted((left, right) => left.publicKey.localeCompare(right.publicKey));
209
- }
210
- #buildUserEntry(publicKey) {
211
- const profile = this.#profiles.get(publicKey);
212
- return {
213
- kind: "user",
214
- id: publicKey,
215
- name: profile?.displayName ?? (publicKey === this.#publicKey ? this.#fallbackProfileName : fallbackPublicKeyLabel(publicKey)),
216
- handle: profile?.handle,
217
- avatarUrl: profile?.avatarUrl,
218
- raw: { publicKey }
219
- };
220
- }
221
- #buildRoomEntry(roomId) {
222
- const room = this.#rooms.get(roomId);
223
- return {
224
- kind: "group",
225
- id: buildBuzzTarget(roomId),
226
- name: room?.name ?? roomId,
227
- handle: room?.name ? `#${room.name}` : void 0,
228
- raw: { roomId }
229
- };
230
- }
231
- };
232
- function compareDirectoryEntries(a, b) {
233
- const aLabel = a.name ?? a.handle ?? a.id;
234
- const bLabel = b.name ?? b.handle ?? b.id;
235
- return aLabel.localeCompare(bLabel) || a.id.localeCompare(b.id);
236
- }
237
- //#endregion
238
26
  //#region extensions/buzz/src/directory-relay.ts
239
27
  const BUZZ_ROOM_QUERY_CHUNK_SIZE = 1e3;
240
28
  const PROFILE_SUBSCRIPTION_REPLACED_REASON = "directory profile subscription replaced";
@@ -752,7 +540,7 @@ async function drainBuzzRoomHistoryRange(params) {
752
540
  skipEventIds: params.skipEventIds,
753
541
  signal: params.signal
754
542
  });
755
- if (!page.overLimit) {
543
+ if (!page.overLimit || params.since === params.until) {
756
544
  if (page.events.length === 0) return "complete";
757
545
  const reservation = await params.reserveCapacity(page.events.length);
758
546
  if (!reservation) return "aborted";
@@ -761,17 +549,7 @@ async function drainBuzzRoomHistoryRange(params) {
761
549
  } finally {
762
550
  reservation.release();
763
551
  }
764
- return "complete";
765
- }
766
- if (params.since === params.until) {
767
- const reservation = await params.reserveCapacity(page.events.length);
768
- if (!reservation) return "aborted";
769
- try {
770
- for (const event of page.events) params.onEvent(event, reservation);
771
- } finally {
772
- reservation.release();
773
- }
774
- return "timestamp-over-limit";
552
+ return page.overLimit ? "timestamp-over-limit" : "complete";
775
553
  }
776
554
  const midpoint = Math.floor((params.since + params.until) / 2);
777
555
  const newer = await drainBuzzRoomHistoryRange({
@@ -1216,16 +994,6 @@ async function createBuzzRoomMembershipTracker(params) {
1216
994
  };
1217
995
  }
1218
996
  //#endregion
1219
- //#region extensions/buzz/src/subscription-budget.ts
1220
- const BUZZ_DIRECTORY_MAX_PROFILE_SUBSCRIPTIONS = 10;
1221
- const BUZZ_MAX_CONFIGURED_ROOMS = 1020;
1222
- function resolveBuzzSubscriptionBudget(roomCount) {
1223
- if (!Number.isSafeInteger(roomCount) || roomCount < 0) throw new Error("Buzz configured room count must be a non-negative integer");
1224
- const availableProfileSubscriptions = 1020 - roomCount;
1225
- if (availableProfileSubscriptions < 0) throw new Error(`Buzz supports at most ${BUZZ_MAX_CONFIGURED_ROOMS} configured rooms per account`);
1226
- return { profileLimit: Math.min(BUZZ_DIRECTORY_MAX_PROFILE_SUBSCRIPTIONS, availableProfileSubscriptions) * 200 };
1227
- }
1228
- //#endregion
1229
997
  //#region extensions/buzz/src/buzz-bus.ts
1230
998
  const PRESENCE_KIND = 20001;
1231
999
  const PRESENCE_HEARTBEAT_INTERVAL_MS = 3e4;
@@ -1233,6 +1001,7 @@ const REPLAY_TTL_MS = 2592e6;
1233
1001
  const REPLAY_MAX_ENTRIES = 1e4;
1234
1002
  const REPLAY_STATE_MAX_ENTRIES = 5e4;
1235
1003
  const REPLAY_NAMESPACE_PREFIX = "buzz.inbound-dedupe";
1004
+ const THREAD_ROOT_CACHE_MAX_ENTRIES = 1024;
1236
1005
  function buildBuzzTextEvent(params) {
1237
1006
  return finalizeEvent({
1238
1007
  kind: 9,
@@ -1396,10 +1165,55 @@ async function startBuzzBus(options) {
1396
1165
  let stopPresenceHeartbeat = () => {};
1397
1166
  let profileTask;
1398
1167
  let membershipTracker;
1168
+ const threadRoots = /* @__PURE__ */ new Map();
1169
+ const rememberThreadRoot = (event) => {
1170
+ const root = parseBuzzMessageEvent(event);
1171
+ if (!root || root.threadId || !verifyEvent(event)) return;
1172
+ threadRoots.set(event.id, {
1173
+ channelId: root.channelId.toLowerCase(),
1174
+ isBotOwned: event.pubkey === publicKey
1175
+ });
1176
+ if (threadRoots.size > THREAD_ROOT_CACHE_MAX_ENTRIES) {
1177
+ const oldest = threadRoots.keys().next().value;
1178
+ if (oldest) threadRoots.delete(oldest);
1179
+ }
1180
+ };
1399
1181
  const bus = {
1400
1182
  publicKey,
1401
1183
  directory,
1402
1184
  refreshDirectory: async () => await directoryRelay?.refreshRooms(options.channelIds),
1185
+ isBotOwnedThread: async ({ channelId, threadId }) => {
1186
+ signal.throwIfAborted();
1187
+ if (!threadRoots.has(threadId)) try {
1188
+ await queryBuzzRelaySnapshot({
1189
+ relay,
1190
+ filters: [{
1191
+ ids: [threadId],
1192
+ kinds: [...BUZZ_INBOUND_MESSAGE_KINDS],
1193
+ limit: 1
1194
+ }],
1195
+ signal,
1196
+ timeoutMessage: "Timed out loading Buzz thread root",
1197
+ abortMessage: "Buzz thread root query aborted",
1198
+ failureMessage: "Buzz thread root query failed",
1199
+ closeReason: "thread root loaded",
1200
+ closeMessage: (reason) => `Buzz thread root query closed: ${reason}`,
1201
+ onEvent: (event) => {
1202
+ if (event.id === threadId) rememberThreadRoot(event);
1203
+ },
1204
+ result: () => {},
1205
+ onTimeout: reportFatalError,
1206
+ checkAbortAfterSubscribe: true
1207
+ });
1208
+ } catch (error) {
1209
+ signal.throwIfAborted();
1210
+ options.onMessageError?.(error instanceof Error ? error : new Error("Buzz thread root query failed", { cause: error }));
1211
+ return false;
1212
+ }
1213
+ signal.throwIfAborted();
1214
+ const root = threadRoots.get(threadId);
1215
+ return root?.channelId === channelId && root.isBotOwned;
1216
+ },
1403
1217
  sendText: async ({ channelId, text, threadId, replyToId }) => {
1404
1218
  signal.throwIfAborted();
1405
1219
  const mentionSyntax = inspectBuzzMentionSyntax(text);
@@ -1417,6 +1231,7 @@ async function startBuzzBus(options) {
1417
1231
  mentionedPubkeys
1418
1232
  });
1419
1233
  await relay.publish(event);
1234
+ rememberThreadRoot(event);
1420
1235
  return event.id;
1421
1236
  },
1422
1237
  sendTyping: async ({ channelId, threadId, replyToId }) => {
@@ -1435,6 +1250,7 @@ async function startBuzzBus(options) {
1435
1250
  stopPresenceHeartbeat();
1436
1251
  directoryRelay?.close();
1437
1252
  replayGuard.clearMemory();
1253
+ threadRoots.clear();
1438
1254
  relay.close();
1439
1255
  await membershipTracker?.close();
1440
1256
  await profileTask;
@@ -1481,7 +1297,11 @@ async function startBuzzBus(options) {
1481
1297
  onHistoryError: options.onHistoryError,
1482
1298
  onRoomUnavailable: options.onRoomUnavailable,
1483
1299
  onMessageEvent: (event, isMember, reservation) => {
1484
- if (signal.aborted || event.pubkey === publicKey) return;
1300
+ if (signal.aborted) return;
1301
+ if (event.pubkey === publicKey) {
1302
+ rememberThreadRoot(event);
1303
+ return;
1304
+ }
1485
1305
  const message = parseBuzzMessageEvent(event);
1486
1306
  if (!message || !isMember(message.channelId, event.pubkey)) return;
1487
1307
  if ((reservation ?? dispatchQueue).enqueue(async () => {
@@ -1545,6 +1365,18 @@ async function startBuzzBus(options) {
1545
1365
  }
1546
1366
  }
1547
1367
  //#endregion
1368
+ //#region src/channels/mention-gating.ts
1369
+ function resolveBotThreadMentionPolicy(params) {
1370
+ if (!params.isBotOwnedThread || params.requireMentionInBotThreads === void 0) return {
1371
+ requireMention: params.requireMention,
1372
+ implicitMentionKinds: params.implicitMentionKinds
1373
+ };
1374
+ return {
1375
+ requireMention: params.requireMentionInBotThreads,
1376
+ implicitMentionKinds: params.requireMentionInBotThreads ? params.implicitMentionKinds?.filter((kind) => kind === "native") : params.implicitMentionKinds
1377
+ };
1378
+ }
1379
+ //#endregion
1548
1380
  //#region extensions/buzz/src/pending-history.ts
1549
1381
  const ENTRY_BYTES = 512;
1550
1382
  const CONTEXT_BYTES = 1024;
@@ -1634,6 +1466,17 @@ async function handleBuzzInbound(params) {
1634
1466
  const shouldComputeCommandAuthorized = supportsTextInterpretation && runtime.channel.commands.shouldComputeCommandAuthorized(message.text, cfg);
1635
1467
  const hasControlCommand = shouldComputeCommandAuthorized && runtime.channel.text.hasControlCommand(message.text, cfg);
1636
1468
  const groupConfig = account.config.groups?.[channelId];
1469
+ const requireMention = groupConfig?.requireMention ?? true;
1470
+ const isBotOwnedThread = message.threadId && !wasMentioned && groupConfig?.requireMentionInBotThreads !== void 0 && groupConfig.requireMentionInBotThreads !== requireMention ? await bus.isBotOwnedThread({
1471
+ channelId,
1472
+ threadId: message.threadId
1473
+ }) : false;
1474
+ params.assertCurrent();
1475
+ const mentionPolicy = resolveBotThreadMentionPolicy({
1476
+ isBotOwnedThread,
1477
+ requireMentionInBotThreads: groupConfig?.requireMentionInBotThreads,
1478
+ requireMention
1479
+ });
1637
1480
  const access = await runtime.channel.inbound.ingress.resolveStable({
1638
1481
  channelId: "buzz",
1639
1482
  accountId: account.accountId,
@@ -1661,7 +1504,7 @@ async function handleBuzzInbound(params) {
1661
1504
  groupPolicy: groupConfig?.groupPolicy ?? account.config.groupPolicy,
1662
1505
  groupAllowFrom: groupConfig?.groupAllowFrom ?? account.config.groupAllowFrom,
1663
1506
  policy: { activation: {
1664
- requireMention: groupConfig?.requireMention ?? true,
1507
+ requireMention: mentionPolicy.requireMention,
1665
1508
  allowTextCommands: true
1666
1509
  } },
1667
1510
  command: shouldComputeCommandAuthorized ? {
@@ -0,0 +1,24 @@
1
+ import { i as resolveBuzzAccount, m as parseBuzzTarget, u as buildBuzzTarget } from "./types-DRUZMyTx.mjs";
2
+ import { t as applyBuzzDirectoryQueryAndLimit } from "./directory-query-D1BNu8nO.mjs";
3
+ //#region extensions/buzz/src/directory-config.ts
4
+ async function listBuzzDirectoryPeersFromConfig(_params) {
5
+ return [];
6
+ }
7
+ async function listBuzzDirectoryGroupsFromConfig(params) {
8
+ const account = resolveBuzzAccount({
9
+ cfg: params.cfg,
10
+ accountId: params.accountId
11
+ });
12
+ const entries = Object.entries(account.config.groups ?? {}).filter(([, config]) => config.enabled !== false).map(([roomId]) => {
13
+ const id = parseBuzzTarget(roomId);
14
+ return {
15
+ kind: "group",
16
+ id: buildBuzzTarget(id),
17
+ name: id,
18
+ raw: { roomId: id }
19
+ };
20
+ }).toSorted((a, b) => a.id.localeCompare(b.id));
21
+ return applyBuzzDirectoryQueryAndLimit(entries, params);
22
+ }
23
+ //#endregion
24
+ export { listBuzzDirectoryPeersFromConfig as n, listBuzzDirectoryGroupsFromConfig as t };
@@ -0,0 +1,22 @@
1
+ //#region extensions/buzz/src/directory-query.ts
2
+ function matchesDirectoryQuery(entry, query) {
3
+ if (!query) return true;
4
+ return [
5
+ entry.id,
6
+ entry.name,
7
+ entry.handle
8
+ ].some((value) => value?.toLowerCase().includes(query));
9
+ }
10
+ function applyBuzzDirectoryQueryAndLimit(entries, params) {
11
+ const query = params.query?.trim().toLowerCase() ?? "";
12
+ const limit = typeof params.limit === "number" && params.limit > 0 ? Math.floor(params.limit) : void 0;
13
+ const result = [];
14
+ for (const entry of entries) {
15
+ if (!matchesDirectoryQuery(entry, query)) continue;
16
+ result.push(entry);
17
+ if (limit !== void 0 && result.length >= limit) break;
18
+ }
19
+ return result;
20
+ }
21
+ //#endregion
22
+ export { applyBuzzDirectoryQueryAndLimit as t };