@openclaw/clickclack 2026.10.1 → 2026.10.5-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.
@@ -1,6 +1,6 @@
1
1
  import { n as __reExport, t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
2
- import { c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, f as isClickClackAccountCurrent, g as resolveClickClackAccountConfig, h as resolveClickClackAccount, l as clickClackConfigAdapter, m as listEnabledClickClackAccounts, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-DcF_7-VV.mjs";
3
- import { a as createClickClackClient, i as ClickClackHttpError, n as resolveChannelId, r as resolveWorkspaceId, s as normalizeClickClackCorrelationId } from "./resolve-DPtjHQHt.mjs";
2
+ import { c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, f as isClickClackAccountCurrent, g as resolveClickClackAccountConfig, h as resolveClickClackAccount, l as clickClackConfigAdapter, m as listEnabledClickClackAccounts, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BJ5D55LV.mjs";
3
+ import { a as createClickClackClient, i as ClickClackHttpError, n as resolveChannelId, r as resolveWorkspaceId, s as normalizeClickClackCorrelationId } from "./resolve-Br3ylpSz.mjs";
4
4
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-CF7TKtkU.mjs";
5
5
  import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/account-helpers";
6
6
  import { parseDateStringTimestampMs } from "openclaw/plugin-sdk/number-runtime";
@@ -22,7 +22,7 @@ import { createAsyncLock } from "openclaw/plugin-sdk/async-lock-runtime";
22
22
  import { truncateCodePoints } from "openclaw/plugin-sdk/text-utility-runtime";
23
23
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
24
24
  import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
25
- import { buildChannelInboundEventContext, createChannelInboundEnvelopeBuilder, createChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression } from "openclaw/plugin-sdk/channel-inbound";
25
+ import { buildChannelInboundEventContext, createChannelInboundEnvelopeBuilderAsync, createChannelPartialDeliveryError, recordChannelBotPairLoopAndCheckSuppression } from "openclaw/plugin-sdk/channel-inbound";
26
26
  import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
27
27
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
28
28
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
@@ -174,61 +174,6 @@ async function hasPendingDiscussionOpenForDestination(params) {
174
174
  return (await listPendingDiscussionOpens(params.runtime)).some((pending) => pending.serverBaseUrl === serverBaseUrl && pending.workspaceId === params.workspaceId);
175
175
  }
176
176
  //#endregion
177
- //#region extensions/clickclack/src/discussions/naming.ts
178
- function shortSessionHash(sessionKey) {
179
- return createHash("sha256").update(sessionKey).digest("hex").slice(0, 32);
180
- }
181
- function slugifyLabel(label) {
182
- return label.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80).replace(/-+$/gu, "");
183
- }
184
- function fallbackDiscussionLabel(sessionKey, agentId) {
185
- const agentSegment = agentId?.trim() ? slugifyLabel(agentId) : "";
186
- const hash = shortSessionHash(sessionKey).slice(0, 8);
187
- return `s-${agentSegment ? `${agentSegment}-` : ""}${hash}`;
188
- }
189
- function resolveDiscussionLabel(entry, sessionKey, agentId) {
190
- return entry.label?.trim() || entry.displayName?.trim() || entry.subject?.trim() || fallbackDiscussionLabel(sessionKey, agentId);
191
- }
192
- function truncateDiscussionDisplayTitle(label) {
193
- return truncateCodePoints(label, 200).trim();
194
- }
195
- function slugifyDiscussionLabel(label, sessionKey) {
196
- return slugifyLabel(label) || fallbackDiscussionLabel(sessionKey);
197
- }
198
- function discussionSessionPeerId(identity) {
199
- return `disc-${createHash("sha256").update([
200
- identity.mainSessionKey,
201
- identity.sessionId,
202
- identity.serverBaseUrl,
203
- identity.channelId,
204
- identity.externalRef
205
- ].join("\0")).digest("hex").slice(0, 32)}`;
206
- }
207
- function isDiscussionSessionKey(sessionKey) {
208
- return sessionKey.includes(":clickclack:") && /:channel:disc-[0-9a-f]{32}$/u.test(sessionKey);
209
- }
210
- function discussionExternalRef(installationId, mainSessionKey, destinationIdentity, bindingGeneration) {
211
- return `openclaw:${installationId}:${shortSessionHash([
212
- mainSessionKey,
213
- destinationIdentity,
214
- bindingGeneration
215
- ].join("\0"))}`;
216
- }
217
- function discussionCredentialFingerprint(token) {
218
- return createHash("sha256").update(token).digest("hex");
219
- }
220
- function discussionSessionKey(params) {
221
- return params.runtime.channel.routing.buildAgentSessionKey({
222
- agentId: params.agentId,
223
- channel: "clickclack",
224
- accountId: params.accountId,
225
- peer: {
226
- kind: "channel",
227
- id: discussionSessionPeerId(params)
228
- }
229
- });
230
- }
231
- //#endregion
232
177
  //#region extensions/clickclack/src/discussions/binding-store.ts
233
178
  function bindingMatchesActiveSessionIncarnation(runtime, sessionKey, binding) {
234
179
  const entry = runtime.agent.session.getSessionEntry({
@@ -237,6 +182,13 @@ function bindingMatchesActiveSessionIncarnation(runtime, sessionKey, binding) {
237
182
  });
238
183
  return Boolean(entry && binding.sessionId && entry.sessionId === binding.sessionId && entry.archivedAt === void 0);
239
184
  }
185
+ async function readDiscussionSessionEntry(runtime, sessionKey) {
186
+ const params = {
187
+ sessionKey,
188
+ readConsistency: "latest"
189
+ };
190
+ return runtime.agent.session.getSessionEntryAsync ? await runtime.agent.session.getSessionEntryAsync(params) : runtime.agent.session.getSessionEntry(params);
191
+ }
240
192
  /**
241
193
  * Refresh the replaceable session attachment without changing the durable room identity.
242
194
  * The store registers persisted state before reindexing, so a failed write leaves the
@@ -259,6 +211,11 @@ function attachBindingToCurrentActiveSession(params) {
259
211
  }
260
212
  const DISCUSSION_BINDINGS_NAMESPACE = "discussion-bindings";
261
213
  const MAX_DISCUSSION_BINDINGS = 1e4;
214
+ const BINDING_STORE_OPTIONS = {
215
+ namespace: DISCUSSION_BINDINGS_NAMESPACE,
216
+ maxEntries: MAX_DISCUSSION_BINDINGS,
217
+ overflowPolicy: "reject-new"
218
+ };
262
219
  const MAX_RETAINED_DETACHED_DISCUSSION_BINDINGS = 1e3;
263
220
  const storesByRuntime$1 = /* @__PURE__ */ new WeakMap();
264
221
  function channelKey(serverBaseUrl, channelId) {
@@ -267,31 +224,36 @@ function channelKey(serverBaseUrl, channelId) {
267
224
  /** SQLite-backed session/channel bindings with a process-local inbound lookup index. */
268
225
  var ClickClackDiscussionBindingStore = class {
269
226
  #store;
227
+ #asyncStore;
270
228
  #sessionByChannel = /* @__PURE__ */ new Map();
271
- #mainByDiscussionSession = /* @__PURE__ */ new Map();
272
229
  #detachedAtBySession = /* @__PURE__ */ new Map();
230
+ #channelBySession = /* @__PURE__ */ new Map();
273
231
  #runtime;
232
+ #prepared = false;
233
+ #preparing;
234
+ #changedDuringPreparation = /* @__PURE__ */ new Set();
274
235
  constructor(runtime) {
275
236
  this.#runtime = runtime;
276
- this.#store = runtime.state.openSyncKeyedStore({
277
- namespace: DISCUSSION_BINDINGS_NAMESPACE,
278
- maxEntries: MAX_DISCUSSION_BINDINGS,
279
- overflowPolicy: "reject-new"
280
- });
281
- for (const entry of this.#store.entries()) this.#index(entry.key, entry.value);
237
+ this.#store = runtime.state.openSyncKeyedStore(BINDING_STORE_OPTIONS);
238
+ }
239
+ async prepare() {
240
+ if (!this.#prepared) await this.entries();
241
+ }
242
+ async getAsync(sessionKey) {
243
+ return await this.#workerStore().lookup(sessionKey);
282
244
  }
283
245
  get(sessionKey) {
284
246
  return this.#store.lookup(sessionKey);
285
247
  }
286
- hasCapacity(sessionKey) {
287
- return this.get(sessionKey) !== void 0 || this.count() < MAX_DISCUSSION_BINDINGS;
248
+ async hasCapacity(sessionKey) {
249
+ return await this.getAsync(sessionKey) !== void 0 || await this.countAsync() < MAX_DISCUSSION_BINDINGS;
288
250
  }
289
251
  getByChannel(serverBaseUrl, channelId) {
290
252
  const key = channelKey(serverBaseUrl, channelId);
291
253
  const sessionKey = this.#sessionByChannel.get(key);
292
254
  if (!sessionKey) return;
293
255
  const binding = this.get(sessionKey);
294
- if (!binding) {
256
+ if (!binding || channelKey(binding.serverBaseUrl, binding.channelId) !== key) {
295
257
  this.#sessionByChannel.delete(key);
296
258
  return;
297
259
  }
@@ -301,38 +263,43 @@ var ClickClackDiscussionBindingStore = class {
301
263
  };
302
264
  }
303
265
  set(sessionKey, binding) {
304
- const previous = this.get(sessionKey);
305
266
  this.#store.register(sessionKey, binding);
306
- if (previous) this.#unindex(sessionKey, previous);
267
+ if (!this.#prepared) this.#changedDuringPreparation.add(sessionKey);
268
+ this.#unindex(sessionKey);
307
269
  this.#index(sessionKey, binding);
308
270
  }
309
271
  delete(sessionKey) {
310
- const previous = this.get(sessionKey);
311
272
  const deleted = this.#store.delete(sessionKey);
312
- if (deleted && previous) this.#unindex(sessionKey, previous);
273
+ if (deleted && !this.#prepared) this.#changedDuringPreparation.add(sessionKey);
274
+ if (deleted) this.#unindex(sessionKey);
313
275
  return deleted;
314
276
  }
315
- getByDiscussionSession(sideSessionKey) {
316
- const sessionKey = this.#mainByDiscussionSession.get(sideSessionKey);
317
- if (!sessionKey) return;
318
- const binding = this.get(sessionKey);
319
- return binding ? {
320
- sessionKey,
321
- binding
322
- } : void 0;
323
- }
324
- entries() {
325
- return this.#store.entries().map((entry) => ({
277
+ async entries() {
278
+ const load = async () => (await this.#workerStore().entries()).map((entry) => ({
326
279
  sessionKey: entry.key,
327
280
  binding: entry.value
328
281
  }));
282
+ if (this.#prepared) return await load();
283
+ this.#preparing ??= load().then((entries) => {
284
+ for (const { sessionKey, binding } of entries) if (!this.#changedDuringPreparation.has(sessionKey)) this.#index(sessionKey, binding);
285
+ this.#prepared = true;
286
+ this.#changedDuringPreparation.clear();
287
+ return entries;
288
+ }).finally(() => {
289
+ this.#preparing = void 0;
290
+ });
291
+ return await this.#preparing;
329
292
  }
330
- count() {
331
- return this.#store.count?.() ?? this.#store.entries().length;
293
+ async countAsync() {
294
+ const store = this.#workerStore();
295
+ return store.count ? await store.count() : (await this.entries()).length;
332
296
  }
333
297
  detachedCount() {
334
298
  return this.#detachedAtBySession.size;
335
299
  }
300
+ #workerStore() {
301
+ return this.#asyncStore ??= this.#runtime.state.openKeyedStore(BINDING_STORE_OPTIONS);
302
+ }
336
303
  oldestDetached() {
337
304
  let oldestSessionKey;
338
305
  let oldestDetachedAt = Number.POSITIVE_INFINITY;
@@ -352,33 +319,15 @@ var ClickClackDiscussionBindingStore = class {
352
319
  };
353
320
  }
354
321
  #index(sessionKey, binding) {
355
- this.#sessionByChannel.set(channelKey(binding.serverBaseUrl, binding.channelId), sessionKey);
356
- const sideSessionKey = discussionSessionKey({
357
- runtime: this.#runtime,
358
- agentId: binding.agentId,
359
- mainSessionKey: sessionKey,
360
- sessionId: binding.sessionId,
361
- accountId: binding.accountId,
362
- serverBaseUrl: binding.serverBaseUrl,
363
- channelId: binding.channelId,
364
- externalRef: binding.externalRef
365
- });
366
- if (sideSessionKey) this.#mainByDiscussionSession.set(sideSessionKey, sessionKey);
322
+ const channel = channelKey(binding.serverBaseUrl, binding.channelId);
323
+ this.#channelBySession.set(sessionKey, channel);
324
+ this.#sessionByChannel.set(channel, sessionKey);
367
325
  if (binding.detachedAt !== void 0) this.#detachedAtBySession.set(sessionKey, binding.detachedAt);
368
326
  }
369
- #unindex(sessionKey, binding) {
370
- this.#sessionByChannel.delete(channelKey(binding.serverBaseUrl, binding.channelId));
371
- const sideSessionKey = discussionSessionKey({
372
- runtime: this.#runtime,
373
- agentId: binding.agentId,
374
- mainSessionKey: sessionKey,
375
- sessionId: binding.sessionId,
376
- accountId: binding.accountId,
377
- serverBaseUrl: binding.serverBaseUrl,
378
- channelId: binding.channelId,
379
- externalRef: binding.externalRef
380
- });
381
- if (sideSessionKey) this.#mainByDiscussionSession.delete(sideSessionKey);
327
+ #unindex(sessionKey) {
328
+ const channel = this.#channelBySession.get(sessionKey);
329
+ this.#channelBySession.delete(sessionKey);
330
+ if (channel) this.#sessionByChannel.delete(channel);
382
331
  this.#detachedAtBySession.delete(sessionKey);
383
332
  }
384
333
  };
@@ -390,6 +339,61 @@ function getClickClackDiscussionBindingStore(runtime) {
390
339
  return created;
391
340
  }
392
341
  //#endregion
342
+ //#region extensions/clickclack/src/discussions/naming.ts
343
+ function shortSessionHash(sessionKey) {
344
+ return createHash("sha256").update(sessionKey).digest("hex").slice(0, 32);
345
+ }
346
+ function slugifyLabel(label) {
347
+ return label.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80).replace(/-+$/gu, "");
348
+ }
349
+ function fallbackDiscussionLabel(sessionKey, agentId) {
350
+ const agentSegment = agentId?.trim() ? slugifyLabel(agentId) : "";
351
+ const hash = shortSessionHash(sessionKey).slice(0, 8);
352
+ return `s-${agentSegment ? `${agentSegment}-` : ""}${hash}`;
353
+ }
354
+ function resolveDiscussionLabel(entry, sessionKey, agentId) {
355
+ return entry.label?.trim() || entry.displayName?.trim() || entry.subject?.trim() || fallbackDiscussionLabel(sessionKey, agentId);
356
+ }
357
+ function truncateDiscussionDisplayTitle(label) {
358
+ return truncateCodePoints(label, 200).trim();
359
+ }
360
+ function slugifyDiscussionLabel(label, sessionKey) {
361
+ return slugifyLabel(label) || fallbackDiscussionLabel(sessionKey);
362
+ }
363
+ function discussionSessionPeerId(identity) {
364
+ return `disc-${createHash("sha256").update([
365
+ identity.mainSessionKey,
366
+ identity.sessionId,
367
+ identity.serverBaseUrl,
368
+ identity.channelId,
369
+ identity.externalRef
370
+ ].join("\0")).digest("hex").slice(0, 32)}`;
371
+ }
372
+ function isDiscussionSessionKey(sessionKey) {
373
+ return sessionKey.includes(":clickclack:") && /:channel:disc-[0-9a-f]{32}$/u.test(sessionKey);
374
+ }
375
+ function discussionExternalRef(installationId, mainSessionKey, destinationIdentity, bindingGeneration) {
376
+ return `openclaw:${installationId}:${shortSessionHash([
377
+ mainSessionKey,
378
+ destinationIdentity,
379
+ bindingGeneration
380
+ ].join("\0"))}`;
381
+ }
382
+ function discussionCredentialFingerprint(token) {
383
+ return createHash("sha256").update(token).digest("hex");
384
+ }
385
+ function discussionSessionKey(params) {
386
+ return params.runtime.channel.routing.buildAgentSessionKey({
387
+ agentId: params.agentId,
388
+ channel: "clickclack",
389
+ accountId: params.accountId,
390
+ peer: {
391
+ kind: "channel",
392
+ id: discussionSessionPeerId(params)
393
+ }
394
+ });
395
+ }
396
+ //#endregion
393
397
  //#region extensions/clickclack/src/discussions/eligibility.ts
394
398
  function discussionAccounts(cfg) {
395
399
  return listEnabledClickClackAccounts(cfg).filter((account) => account.configured && account.discussions.enabled);
@@ -470,6 +474,7 @@ function isClickClackDiscussionChannelRevoked(params) {
470
474
  //#region extensions/clickclack/src/discussions/routing.ts
471
475
  async function resolveClickClackDiscussionRoute(params) {
472
476
  const store = getClickClackDiscussionBindingStore(params.runtime);
477
+ await store.prepare();
473
478
  let matched = store.getByChannel(params.serverBaseUrl, params.channelId);
474
479
  let pending = false;
475
480
  if (!matched) {
@@ -636,17 +641,14 @@ function parseClickClackTarget(raw) {
636
641
  };
637
642
  throw new Error(`Unsupported ClickClack target: ${raw}`);
638
643
  }
639
- /** Formats a parsed ClickClack target back into canonical target syntax. */
640
644
  function buildClickClackTarget(target) {
641
645
  return `${target.kind}:${target.id}`;
642
646
  }
643
- /** Normalizes user-entered ClickClack target text for channel routing. */
644
647
  function normalizeClickClackTarget(raw) {
645
648
  return buildClickClackTarget(parseClickClackTarget(raw));
646
649
  }
647
- /** Reports whether a target string can be offered to the ClickClack parser. */
648
650
  function looksLikeClickClackTarget(raw) {
649
- return /^(channel|thread|dm):/i.test(raw.trim()) || raw.trim().length > 0;
651
+ return raw.trim().length > 0;
650
652
  }
651
653
  //#endregion
652
654
  //#region extensions/clickclack/src/access.ts
@@ -1017,7 +1019,6 @@ const STREAMING_COMMENTARY_ITEM_KINDS = /* @__PURE__ */ new Set([
1017
1019
  "thinking",
1018
1020
  "reasoning"
1019
1021
  ]);
1020
- /** Provider-specific reasoning lanes normalized into one ClickClack label. */
1021
1022
  const THINKING_ITEM_KINDS = /* @__PURE__ */ new Set([
1022
1023
  "analysis",
1023
1024
  "thinking",
@@ -1043,9 +1044,7 @@ function activityBody(payload) {
1043
1044
  const head = payload.name?.trim() || payload.title?.trim();
1044
1045
  const text = payload.progressText?.trim() || payload.summary?.trim();
1045
1046
  if (head && text) return `**${head}**\n\n${text}`;
1046
- if (text) return text;
1047
- if (head) return head;
1048
- return payload.status?.trim() || payload.kind?.trim() || "";
1047
+ return text || head || payload.status?.trim() || payload.kind?.trim() || "";
1049
1048
  }
1050
1049
  /**
1051
1050
  * Creates a per-turn activity publisher. Publishing is best-effort: transport
@@ -1078,7 +1077,7 @@ function createClickClackActivityPublisher(params) {
1078
1077
  clearTimeout(segment.timer);
1079
1078
  segment.timer = void 0;
1080
1079
  }
1081
- if (!segment.dirty || !segment.body.trim()) return Promise.resolve();
1080
+ if (!segment.dirty || !segment.body) return Promise.resolve();
1082
1081
  segment.dirty = false;
1083
1082
  const body = segment.body;
1084
1083
  return enqueue(async () => {
@@ -1098,7 +1097,7 @@ function createClickClackActivityPublisher(params) {
1098
1097
  const body = commentaryBody(payload);
1099
1098
  const key = payload.itemId?.trim() || "turn";
1100
1099
  let segment = commentaryByItem.get(key);
1101
- if (!body.trim()) {
1100
+ if (!body) {
1102
1101
  if (segment) {
1103
1102
  clearTimeout(segment.timer);
1104
1103
  commentaryByItem.delete(key);
@@ -1177,9 +1176,11 @@ function createClickClackActivityPublisher(params) {
1177
1176
  handleDiscreteItem(payload);
1178
1177
  return false;
1179
1178
  },
1179
+ /** Model/thinking from onModelSelected is stamped onto subsequent activity rows. */
1180
1180
  setProvenance: (next) => {
1181
1181
  provenance = next;
1182
1182
  },
1183
+ /** Flushes pending commentary and awaits all outstanding POST/PATCH work. */
1183
1184
  finalize: async () => {
1184
1185
  await flushAllCommentary();
1185
1186
  await chain;
@@ -1258,25 +1259,16 @@ function createOutboundContext(params) {
1258
1259
  cfg: params.cfg,
1259
1260
  accountId: params.accountId
1260
1261
  });
1261
- const assertDirectAdapterHandoff = params.assertDirectAdapterHandoff;
1262
- const fetcher = fetch;
1263
1262
  return {
1264
1263
  account,
1265
1264
  client: createClickClackClient({
1266
1265
  baseUrl: account.apiEndpoint,
1267
1266
  token: account.token,
1268
1267
  correlationId: params.correlationId,
1269
- fetch: assertDirectAdapterHandoff ? (input, init) => {
1270
- assertDirectAdapterHandoff();
1271
- return fetcher(input, init);
1272
- } : void 0
1268
+ beforeRequest: params.assertDirectAdapterHandoff
1273
1269
  })
1274
1270
  };
1275
1271
  }
1276
- /**
1277
- * Sends visible text to a normalized ClickClack target and returns the created
1278
- * message id, or undefined when sanitization removes all content.
1279
- */
1280
1272
  async function sendClickClackText(params) {
1281
1273
  const text = sanitizeAssistantVisibleText(params.text);
1282
1274
  if (!text) return;
@@ -1793,10 +1785,10 @@ async function handleClickClackInbound(params) {
1793
1785
  }
1794
1786
  });
1795
1787
  const senderName = message.author?.display_name || message.author_id;
1796
- const body = createChannelInboundEnvelopeBuilder({
1788
+ const body = (await createChannelInboundEnvelopeBuilderAsync({
1797
1789
  cfg: params.config,
1798
1790
  route
1799
- })({
1791
+ }))({
1800
1792
  channel: "ClickClack",
1801
1793
  from: senderName,
1802
1794
  timestamp: new Date(message.created_at),
@@ -1939,9 +1931,6 @@ const CLICKCLACK_EVENT_PAGE_LIMIT = 500;
1939
1931
  function payloadString(event, key) {
1940
1932
  return readStringField(event.payload, key) ?? "";
1941
1933
  }
1942
- function eventCorrelationId(event) {
1943
- return normalizeClickClackCorrelationId(event.payload?.correlation_id);
1944
- }
1945
1934
  async function resolveEventMessage(params) {
1946
1935
  const messageId = payloadString(params.event, "message_id");
1947
1936
  if (!messageId) return null;
@@ -1962,7 +1951,7 @@ function parseSocketEvent(data) {
1962
1951
  async function processEvent(params) {
1963
1952
  if (params.event.type !== "message.created" && params.event.type !== "thread.reply_created") return;
1964
1953
  if (params.abortSignal.aborted || payloadString(params.event, "author_id") === params.botUserId) return;
1965
- const correlationId = eventCorrelationId(params.event);
1954
+ const correlationId = normalizeClickClackCorrelationId(params.event.payload?.correlation_id);
1966
1955
  const message = await resolveEventMessage({
1967
1956
  client: correlationId ? createClickClackClient({
1968
1957
  baseUrl: params.account.apiEndpoint,
@@ -1998,17 +1987,17 @@ async function processEvent(params) {
1998
1987
  async function drainEventBacklog(params) {
1999
1988
  let afterCursor = params.afterCursor;
2000
1989
  while (!params.abortSignal.aborted) {
2001
- const events = (await params.client.eventPage(params.workspaceId, {
1990
+ const page = await params.client.eventPage(params.workspaceId, {
2002
1991
  afterCursor,
2003
1992
  limit: CLICKCLACK_EVENT_PAGE_LIMIT
2004
- })).events;
2005
- for (const event of events) {
1993
+ });
1994
+ for (const event of page.events) {
2006
1995
  if (params.abortSignal.aborted) return afterCursor;
2007
1996
  if (!event.cursor || event.cursor === afterCursor) throw new Error("ClickClack event backlog returned a non-advancing cursor");
2008
1997
  await params.onEvent(event);
2009
1998
  afterCursor = event.cursor;
2010
1999
  }
2011
- if (events.length === 0) return afterCursor;
2000
+ if (page.events.length === 0) return afterCursor;
2012
2001
  }
2013
2002
  return afterCursor;
2014
2003
  }
@@ -2334,4 +2323,4 @@ const clickClackPlugin = createChatChannelPlugin({
2334
2323
  }
2335
2324
  });
2336
2325
  //#endregion
2337
- export { slugifyDiscussionLabel as C, listPendingDiscussionOpens as D, clearPendingDiscussionOpen as E, recordPendingDiscussionOpen as O, resolveDiscussionLabel as S, clearDiscussionBindingGeneration as T, getClickClackDiscussionBindingStore as _, setClickClackRuntime as a, fallbackDiscussionLabel as b, markClickClackDiscussionChannelIdentityRevoked as c, discussionInfoForBinding as d, normalizedServerBaseUrl as f, bindingMatchesActiveSessionIncarnation as g, attachBindingToCurrentActiveSession as h, getClickClackRuntime as i, reserveDiscussionBindingGeneration as k, markClickClackDiscussionChannelRevoked as l, MAX_RETAINED_DETACHED_DISCUSSION_BINDINGS as m, buildClickClackTarget as n, clearClickClackDiscussionChannelRevoked as o, resolveDiscussionBindingAccount as p, parseClickClackTarget as r, isClickClackDiscussionChannelRevoked as s, clickClackPlugin as t, discussionAccounts as u, discussionCredentialFingerprint as v, truncateDiscussionDisplayTitle as w, isDiscussionSessionKey as x, discussionExternalRef as y };
2326
+ export { recordPendingDiscussionOpen as A, attachBindingToCurrentActiveSession as C, clearDiscussionBindingGeneration as D, readDiscussionSessionEntry as E, clearPendingDiscussionOpen as O, MAX_RETAINED_DETACHED_DISCUSSION_BINDINGS as S, getClickClackDiscussionBindingStore as T, fallbackDiscussionLabel as _, setClickClackRuntime as a, slugifyDiscussionLabel as b, markClickClackDiscussionChannelIdentityRevoked as c, discussionInfoForBinding as d, normalizedServerBaseUrl as f, discussionSessionKey as g, discussionExternalRef as h, getClickClackRuntime as i, reserveDiscussionBindingGeneration as j, listPendingDiscussionOpens as k, markClickClackDiscussionChannelRevoked as l, discussionCredentialFingerprint as m, buildClickClackTarget as n, clearClickClackDiscussionChannelRevoked as o, resolveDiscussionBindingAccount as p, parseClickClackTarget as r, isClickClackDiscussionChannelRevoked as s, clickClackPlugin as t, discussionAccounts as u, isDiscussionSessionKey as v, bindingMatchesActiveSessionIncarnation as w, truncateDiscussionDisplayTitle as x, resolveDiscussionLabel as y };
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { bufferToBlobPart } from "openclaw/plugin-sdk/blob-runtime";
3
+ import { captureEffectAuthority } from "openclaw/plugin-sdk/fetch-runtime";
3
4
  import { redactToolPayloadText } from "openclaw/plugin-sdk/logging-core";
4
5
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
6
  import path from "node:path";
@@ -72,10 +73,13 @@ function createClickClackClient(options) {
72
73
  const controller = requestOptions.timeoutMs !== void 0 && !init.signal ? new AbortController() : void 0;
73
74
  const timeout = controller ? setTimeout(() => controller.abort(), requestOptions.timeoutMs) : void 0;
74
75
  try {
75
- const response = await fetcher(`${baseUrl}${path}`, {
76
- ...init,
77
- ...controller ? { signal: controller.signal } : {},
78
- headers: requestHeaders
76
+ const response = await captureEffectAuthority().initiate(() => {
77
+ options.beforeRequest?.();
78
+ return fetcher(`${baseUrl}${path}`, {
79
+ ...init,
80
+ ...controller ? { signal: controller.signal } : {},
81
+ headers: requestHeaders
82
+ });
79
83
  });
80
84
  if (!response.ok) {
81
85
  const detail = await readResponseTextLimited(response, CLICKCLACK_ERROR_BODY_LIMIT_BYTES);
@@ -132,6 +136,9 @@ function createClickClackClient(options) {
132
136
  throw error;
133
137
  }
134
138
  }
139
+ function messageList(resource) {
140
+ return async (id, afterSeq, limit = 20) => requestObject("messages", `/api/${resource}/${encodeURIComponent(id)}/messages?after_seq=${afterSeq}&limit=${limit}`);
141
+ }
135
142
  return {
136
143
  me: async () => requestObject("user", "/api/me"),
137
144
  setBotCommands: async (commands) => requestObject("bot_commands", "/api/bots/self/commands", {
@@ -148,7 +155,7 @@ function createClickClackClient(options) {
148
155
  method: "PATCH",
149
156
  body: JSON.stringify(patch)
150
157
  }),
151
- channelMessages: async (channelId, afterSeq, limit = 20) => requestObject("messages", `/api/channels/${encodeURIComponent(channelId)}/messages?after_seq=${afterSeq}&limit=${limit}`),
158
+ channelMessages: messageList("channels"),
152
159
  latestChannelMessages: async (channelId, limit = 30) => {
153
160
  const boundedLimit = Math.max(1, Math.min(CLICKCLACK_MESSAGE_PAGE_LIMIT, limit));
154
161
  let beforeSeq;
@@ -191,7 +198,7 @@ function createClickClackClient(options) {
191
198
  beforeSeq = page.oldest_seq;
192
199
  }
193
200
  },
194
- directMessages: async (conversationId, afterSeq, limit = 20) => requestObject("messages", `/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`),
201
+ directMessages: messageList("dms"),
195
202
  thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
196
203
  message: async (messageId) => requestObject("message", `/api/messages/${encodeURIComponent(messageId)}`),
197
204
  findMessageByNonce: async (params) => findByNonce("message", params),
@@ -1,6 +1,6 @@
1
- import { h as resolveClickClackAccount, p as listClickClackAccountIds } from "./setup-surface-DcF_7-VV.mjs";
2
- import { C as slugifyDiscussionLabel, D as listPendingDiscussionOpens, E as clearPendingDiscussionOpen, O as recordPendingDiscussionOpen, S as resolveDiscussionLabel, T as clearDiscussionBindingGeneration, _ as getClickClackDiscussionBindingStore, b as fallbackDiscussionLabel, c as markClickClackDiscussionChannelIdentityRevoked, d as discussionInfoForBinding, f as normalizedServerBaseUrl, g as bindingMatchesActiveSessionIncarnation, h as attachBindingToCurrentActiveSession, k as reserveDiscussionBindingGeneration, l as markClickClackDiscussionChannelRevoked, o as clearClickClackDiscussionChannelRevoked, p as resolveDiscussionBindingAccount, s as isClickClackDiscussionChannelRevoked, u as discussionAccounts, v as discussionCredentialFingerprint, w as truncateDiscussionDisplayTitle, x as isDiscussionSessionKey, y as discussionExternalRef } from "./channel-DI3hKiSg.mjs";
3
- import { a as createClickClackClient, i as ClickClackHttpError, o as isClickClackChannelNameConflict, t as findClickClackWorkspace } from "./resolve-DPtjHQHt.mjs";
1
+ import { h as resolveClickClackAccount, p as listClickClackAccountIds } from "./setup-surface-BJ5D55LV.mjs";
2
+ import { A as recordPendingDiscussionOpen, C as attachBindingToCurrentActiveSession, D as clearDiscussionBindingGeneration, E as readDiscussionSessionEntry, O as clearPendingDiscussionOpen, T as getClickClackDiscussionBindingStore, _ as fallbackDiscussionLabel, b as slugifyDiscussionLabel, c as markClickClackDiscussionChannelIdentityRevoked, d as discussionInfoForBinding, f as normalizedServerBaseUrl, g as discussionSessionKey, h as discussionExternalRef, j as reserveDiscussionBindingGeneration, k as listPendingDiscussionOpens, l as markClickClackDiscussionChannelRevoked, m as discussionCredentialFingerprint, o as clearClickClackDiscussionChannelRevoked, p as resolveDiscussionBindingAccount, s as isClickClackDiscussionChannelRevoked, u as discussionAccounts, v as isDiscussionSessionKey, w as bindingMatchesActiveSessionIncarnation, x as truncateDiscussionDisplayTitle, y as resolveDiscussionLabel } from "./channel-BFuhVeiX.mjs";
3
+ import { a as createClickClackClient, i as ClickClackHttpError, o as isClickClackChannelNameConflict, t as findClickClackWorkspace } from "./resolve-Br3ylpSz.mjs";
4
4
  import "../api.js";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { createAsyncLock } from "openclaw/plugin-sdk/async-lock-runtime";
@@ -36,15 +36,15 @@ var DetachedDiscussionBindingRetention = class {
36
36
  this.#store.set(sessionKey, retained);
37
37
  return retained;
38
38
  }
39
- ensureCapacity(sessionKey) {
40
- while (!this.#store.hasCapacity(sessionKey)) if (!this.#pruneOldest()) throw new Error("ClickClack discussion binding capacity is exhausted");
39
+ async ensureCapacity(sessionKey) {
40
+ await this.#store.prepare();
41
+ while (!await this.#store.hasCapacity(sessionKey)) if (!this.#pruneOldest()) throw new Error("ClickClack discussion binding capacity is exhausted");
41
42
  }
42
43
  #pruneOldest() {
43
44
  for (;;) {
44
45
  const oldest = this.#store.oldestDetached();
45
46
  if (!oldest) return false;
46
- const current = this.#store.get(oldest.sessionKey);
47
- if (!current || current.detachedAt === void 0) continue;
47
+ const current = oldest.binding;
48
48
  if (this.#runtime.agent.session.getSessionEntry({
49
49
  sessionKey: oldest.sessionKey,
50
50
  readConsistency: "latest"
@@ -242,10 +242,7 @@ function assertManagedChannelListContract(channels) {
242
242
  }
243
243
  async function openClickClackDiscussionBinding(params) {
244
244
  const { account, runtime, sessionKey, store } = params;
245
- const entry = runtime.agent.session.getSessionEntry({
246
- sessionKey,
247
- readConsistency: "latest"
248
- });
245
+ const entry = await readDiscussionSessionEntry(runtime, sessionKey);
249
246
  if (!entry || entry.archivedAt !== void 0) return;
250
247
  if (!entry.sessionId?.trim()) throw new Error("OpenClaw session does not yet have a concrete session id");
251
248
  const client = params.clientFactory(account);
@@ -255,6 +252,12 @@ async function openClickClackDiscussionBinding(params) {
255
252
  if (!workspace.route_id) throw new Error("ClickClack discussions workspace is missing its route id");
256
253
  const serverBaseUrl = normalizedServerBaseUrl(account);
257
254
  const credentialFingerprint = discussionCredentialFingerprint(account.token);
255
+ const revokeChannel = (channelId) => markClickClackDiscussionChannelIdentityRevoked({
256
+ runtime,
257
+ accountId: account.accountId,
258
+ serverBaseUrl,
259
+ channelId
260
+ });
258
261
  const unresolved = (await listPendingDiscussionOpens(runtime)).find((pending) => pending.sessionKey === sessionKey);
259
262
  if (unresolved && (unresolved.accountId !== account.accountId || unresolved.credentialFingerprint !== credentialFingerprint || unresolved.serverBaseUrl !== serverBaseUrl || unresolved.workspaceId !== workspace.id)) {
260
263
  await params.reconcilePendingOpen(unresolved);
@@ -266,10 +269,16 @@ async function openClickClackDiscussionBinding(params) {
266
269
  sessionKey
267
270
  });
268
271
  const fallback = fallbackDiscussionLabel(sessionKey, agentId);
269
- const label = resolveDiscussionLabel(entry, sessionKey, agentId);
270
- const displayTitle = label === fallback ? "" : truncateDiscussionDisplayTitle(label);
271
- const section = entry.category?.trim() || account.discussions.section;
272
- const externalUrl = controlSessionUrl(account.discussions.controlUrlBase, sessionKey, account.agentId ?? agentId, config.session?.mainKey, label);
272
+ const describeChannel = (session) => {
273
+ const label = resolveDiscussionLabel(session, sessionKey, agentId);
274
+ return {
275
+ label,
276
+ displayTitle: label === fallback ? "" : truncateDiscussionDisplayTitle(label),
277
+ section: session.category?.trim() || account.discussions.section,
278
+ externalUrl: controlSessionUrl(account.discussions.controlUrlBase, sessionKey, account.agentId ?? agentId, config.session?.mainKey, label)
279
+ };
280
+ };
281
+ const { label, displayTitle, section, externalUrl } = describeChannel(entry);
273
282
  const assertCurrentAuthority = () => {
274
283
  const currentAccounts = discussionAccounts(runtime.config.current());
275
284
  const currentAccount = currentAccounts.length === 1 ? currentAccounts[0] : void 0;
@@ -278,9 +287,10 @@ async function openClickClackDiscussionBinding(params) {
278
287
  readConsistency: "latest"
279
288
  });
280
289
  if (!currentAccount || currentAccount.accountId !== account.accountId || normalizedServerBaseUrl(currentAccount) !== serverBaseUrl || currentAccount.apiEndpoint !== account.apiEndpoint || discussionCredentialFingerprint(currentAccount.token) !== credentialFingerprint || currentAccount.discussions.workspace !== account.discussions.workspace || !currentEntry?.sessionId || currentEntry.archivedAt !== void 0) throw new Error("ClickClack discussion authority changed while opening the channel");
290
+ return currentEntry;
281
291
  };
282
292
  return await params.withChannelMutationLock(async () => {
283
- params.ensureBindingCapacity(sessionKey);
293
+ await params.ensureBindingCapacity(sessionKey);
284
294
  let channels = await client.channels(workspace.id);
285
295
  assertManagedChannelListContract(channels);
286
296
  const destinationIdentity = [serverBaseUrl, workspace.id].join("\0");
@@ -336,24 +346,14 @@ async function openClickClackDiscussionBinding(params) {
336
346
  assertCurrentAuthority();
337
347
  try {
338
348
  if (adopted) {
339
- markClickClackDiscussionChannelIdentityRevoked({
340
- runtime,
341
- accountId: account.accountId,
342
- serverBaseUrl,
343
- channelId: adopted.id
344
- });
349
+ revokeChannel(adopted.id);
345
350
  resolved = await client.updateChannel(adopted.id, managedFields);
346
351
  } else {
347
352
  resolved = await client.createChannel(workspace.id, {
348
353
  ...managedFields,
349
354
  kind: "public"
350
355
  });
351
- markClickClackDiscussionChannelIdentityRevoked({
352
- runtime,
353
- accountId: account.accountId,
354
- serverBaseUrl,
355
- channelId: resolved.id
356
- });
356
+ revokeChannel(resolved.id);
357
357
  }
358
358
  break;
359
359
  } catch (error) {
@@ -369,12 +369,7 @@ async function openClickClackDiscussionBinding(params) {
369
369
  const recovered = relisted.find((candidate) => candidate.external_managed === true && candidate.external_ref === externalRef);
370
370
  if (recovered) {
371
371
  adopted = recovered;
372
- markClickClackDiscussionChannelIdentityRevoked({
373
- runtime,
374
- accountId: account.accountId,
375
- serverBaseUrl,
376
- channelId: recovered.id
377
- });
372
+ revokeChannel(recovered.id);
378
373
  assertCurrentAuthority();
379
374
  resolved = await client.updateChannel(recovered.id, managedFields);
380
375
  break;
@@ -407,19 +402,14 @@ async function openClickClackDiscussionBinding(params) {
407
402
  throw new Error("ClickClack discussion channel is missing its route id");
408
403
  }
409
404
  const channel = resolved;
410
- const currentEntry = runtime.agent.session.getSessionEntry({
411
- sessionKey,
412
- readConsistency: "latest"
413
- });
405
+ const currentEntry = await readDiscussionSessionEntry(runtime, sessionKey);
414
406
  if (!currentEntry?.sessionId || currentEntry.archivedAt !== void 0) {
415
407
  await clearPendingDiscussionOpen(generationScope);
416
408
  params.warn(`unattached discussion channel remains quarantined: ${channel.id}`);
417
409
  throw new Error("OpenClaw session became inactive while opening its ClickClack discussion");
418
410
  }
419
- const currentLabel = resolveDiscussionLabel(currentEntry, sessionKey, agentId);
420
- const currentDisplayTitle = currentLabel === fallback ? "" : truncateDiscussionDisplayTitle(currentLabel);
421
- const currentSection = currentEntry.category?.trim() || account.discussions.section;
422
- const currentExternalUrl = controlSessionUrl(account.discussions.controlUrlBase, sessionKey, account.agentId ?? agentId, config.session?.mainKey, currentLabel) ?? "";
411
+ const { label: currentLabel, displayTitle: currentDisplayTitle, section: currentSection, externalUrl: updatedExternalUrl } = describeChannel(currentEntry);
412
+ const currentExternalUrl = updatedExternalUrl ?? "";
423
413
  let currentChannel = channel;
424
414
  if (currentEntry.sessionId !== entry.sessionId || currentLabel !== label || currentDisplayTitle !== displayTitle || currentSection !== section || currentExternalUrl !== (externalUrl ?? "")) try {
425
415
  currentChannel = await renameDiscussionChannel({
@@ -461,7 +451,7 @@ async function openClickClackDiscussionBinding(params) {
461
451
  ...currentChannel.display_title !== void 0 ? { displayTitle: currentChannel.display_title } : {}
462
452
  };
463
453
  try {
464
- assertCurrentAuthority();
454
+ nextBinding.sessionId = assertCurrentAuthority().sessionId;
465
455
  store.set(sessionKey, nextBinding);
466
456
  } catch (error) {
467
457
  await clearPendingDiscussionOpen(generationScope);
@@ -474,7 +464,7 @@ async function openClickClackDiscussionBinding(params) {
474
464
  }
475
465
  async function reconcilePendingDiscussionOpen(params) {
476
466
  const { pending } = params;
477
- const currentBinding = params.store.get(pending.sessionKey);
467
+ const currentBinding = await params.store.getAsync(pending.sessionKey);
478
468
  if (currentBinding?.externalRef === pending.externalRef) {
479
469
  await params.finalizePendingBinding(pending.sessionKey, currentBinding);
480
470
  return;
@@ -486,10 +476,7 @@ async function reconcilePendingDiscussionOpen(params) {
486
476
  })).find((candidate) => candidate.configured && normalizedServerBaseUrl(candidate) === pending.serverBaseUrl && discussionCredentialFingerprint(candidate.token) === pending.credentialFingerprint);
487
477
  if (!account) return;
488
478
  const client = params.clientFactory(account);
489
- const entry = params.runtime.agent.session.getSessionEntry({
490
- sessionKey: pending.sessionKey,
491
- readConsistency: "latest"
492
- });
479
+ const entry = await readDiscussionSessionEntry(params.runtime, pending.sessionKey);
493
480
  const activeAccounts = discussionAccounts(cfg);
494
481
  const retryAccount = activeAccounts.length === 1 ? activeAccounts[0] : void 0;
495
482
  if (params.allowRetry !== false && entry?.sessionId && entry.archivedAt === void 0 && retryAccount && normalizedServerBaseUrl(retryAccount) === pending.serverBaseUrl && discussionCredentialFingerprint(retryAccount.token) === pending.credentialFingerprint) {
@@ -559,8 +546,8 @@ var ClickClackDiscussionService = class {
559
546
  });
560
547
  this.provider = {
561
548
  id: "clickclack",
562
- info: async ({ sessionKey }) => await this.info(sessionKey),
563
- open: async ({ sessionKey }) => await this.open(sessionKey)
549
+ info: ({ sessionKey }) => this.info(sessionKey),
550
+ open: ({ sessionKey }) => this.open(sessionKey)
564
551
  };
565
552
  }
566
553
  async bindGatewayEvents(gatewayEvents, scheduler) {
@@ -580,7 +567,7 @@ var ClickClackDiscussionService = class {
580
567
  });
581
568
  this.#unsubscribeSessionsChanged = gatewayEvents?.onSessionsChanged((event) => {
582
569
  this.#withOperation(async () => {
583
- if (this.#store.get(event.sessionKey) || (await listPendingDiscussionOpens(this.#runtime)).some((pending) => pending.sessionKey === event.sessionKey)) {
570
+ if (await this.#store.getAsync(event.sessionKey) || (await listPendingDiscussionOpens(this.#runtime)).some((pending) => pending.sessionKey === event.sessionKey)) {
584
571
  if (activation === this.#activation) this.#reconcileScheduler?.schedule(event.sessionKey);
585
572
  }
586
573
  }).catch((error) => {
@@ -600,8 +587,9 @@ var ClickClackDiscussionService = class {
600
587
  }
601
588
  async info(sessionKey) {
602
589
  return await this.#withOperation(() => this.#sessionQueue.enqueue(sessionKey, async () => {
590
+ await this.#store.prepare();
603
591
  if (discussionAccounts(this.#currentConfig()).length !== 1) return { state: "none" };
604
- const existing = this.#store.get(sessionKey);
592
+ const existing = await this.#store.getAsync(sessionKey);
605
593
  if (existing) {
606
594
  const resolved = await this.#resolveBindingForUse(existing);
607
595
  if (resolved.state === "retargeted") {
@@ -615,7 +603,7 @@ var ClickClackDiscussionService = class {
615
603
  if (resolved.state !== "active") return { state: "none" };
616
604
  await this.#finalizePendingBinding(sessionKey, existing);
617
605
  await this.#reconcileBinding(sessionKey, existing, resolved.account);
618
- const current = this.#store.get(sessionKey);
606
+ const current = await this.#store.getAsync(sessionKey);
619
607
  if (!current) return { state: this.hasEnabledAccount() ? "available" : "none" };
620
608
  return discussionInfoForBinding(current, resolved.account);
621
609
  }
@@ -623,7 +611,10 @@ var ClickClackDiscussionService = class {
623
611
  }));
624
612
  }
625
613
  async open(sessionKey) {
626
- return await this.#withOperation(() => this.#sessionQueue.enqueue(sessionKey, () => this.#open(sessionKey)));
614
+ return await this.#withOperation(() => this.#sessionQueue.enqueue(sessionKey, async () => {
615
+ await this.#store.prepare();
616
+ return await this.#open(sessionKey);
617
+ }));
627
618
  }
628
619
  async #open(sessionKey) {
629
620
  this.#installationId ??= await getClickClackDiscussionInstallationId(this.#runtime);
@@ -631,7 +622,7 @@ var ClickClackDiscussionService = class {
631
622
  if (accounts.length > 1) throw new Error("ClickClack discussions require exactly one enabled discussion account");
632
623
  const account = accounts[0];
633
624
  if (!account) return { state: "none" };
634
- const existing = this.#store.get(sessionKey);
625
+ const existing = await this.#store.getAsync(sessionKey);
635
626
  if (existing) {
636
627
  const resolved = await this.#resolveBindingForUse(existing);
637
628
  if (resolved.state === "retargeted") this.#revokeAndDeleteBinding(sessionKey, existing);
@@ -639,7 +630,7 @@ var ClickClackDiscussionService = class {
639
630
  else if (resolved.state === "active") {
640
631
  await this.#finalizePendingBinding(sessionKey, existing);
641
632
  await this.#reconcileBinding(sessionKey, existing, resolved.account);
642
- const current = this.#store.get(sessionKey);
633
+ const current = await this.#store.getAsync(sessionKey);
643
634
  if (current) return discussionInfoForBinding(current, resolved.account);
644
635
  }
645
636
  }
@@ -654,8 +645,8 @@ var ClickClackDiscussionService = class {
654
645
  bindingGenerationFactory: this.#bindingGenerationFactory,
655
646
  sessionKey,
656
647
  ensureTimer: () => this.#ensureTimer(),
657
- reconcilePendingOpen: async (pending) => await this.#reconcilePendingOpen(pending, { allowRetry: false }),
658
- withChannelMutationLock: async (run) => await this.#withChannelMutationLock(run),
648
+ reconcilePendingOpen: (pending) => this.#reconcilePendingOpen(pending, { allowRetry: false }),
649
+ withChannelMutationLock: (run) => this.#withChannelMutationLock(run),
659
650
  ensureBindingCapacity: (key) => this.#detachedBindings.ensureCapacity(key),
660
651
  finalizePendingBinding: (key, nextBinding) => this.#finalizePendingBinding(key, nextBinding),
661
652
  warn: (message) => this.#logger().warn(message)
@@ -672,7 +663,8 @@ var ClickClackDiscussionService = class {
672
663
  async #reconcile(sessionKey) {
673
664
  try {
674
665
  await this.#sessionQueue.enqueue(sessionKey, async () => {
675
- const binding = this.#store.get(sessionKey);
666
+ await this.#store.prepare();
667
+ const binding = await this.#store.getAsync(sessionKey);
676
668
  if (binding) await this.#reconcileBinding(sessionKey, binding);
677
669
  const pending = (await listPendingDiscussionOpens(this.#runtime)).find((candidate) => candidate.sessionKey === sessionKey);
678
670
  if (pending) await this.#reconcilePendingOpen(pending);
@@ -702,7 +694,8 @@ var ClickClackDiscussionService = class {
702
694
  }
703
695
  async readLatestMessages(sessionKey, limit) {
704
696
  return await this.#withOperation(() => this.#sessionQueue.enqueue(sessionKey, async () => {
705
- const binding = this.#store.get(sessionKey);
697
+ await this.#store.prepare();
698
+ const binding = await this.#store.getAsync(sessionKey);
706
699
  if (!binding) return { text: "No discussion is bound to this session." };
707
700
  const resolved = await this.#resolveBindingForUse(binding);
708
701
  if (resolved.state !== "active") return { text: "No discussion is bound to this session." };
@@ -759,10 +752,31 @@ var ClickClackDiscussionService = class {
759
752
  return;
760
753
  }
761
754
  if (resolved.state !== "active") return;
762
- const account = resolved.account;
763
- if (!account.baseUrl || !account.token) throw new Error(`ClickClack discussion account is no longer configured: ${activeBinding.accountId}`);
755
+ if (!resolved.account.baseUrl || !resolved.account.token) throw new Error(`ClickClack discussion account is no longer configured: ${activeBinding.accountId}`);
764
756
  if (entry.archivedAt !== void 0) return;
765
- const attached = this.#refreshSessionAttachment(sessionKey, activeBinding);
757
+ const labelForBackfill = resolveDiscussionLabel(entry, sessionKey, activeBinding.agentId);
758
+ const desiredBackfillTitle = labelForBackfill === fallbackDiscussionLabel(sessionKey, activeBinding.agentId) ? "" : truncateDiscussionDisplayTitle(labelForBackfill);
759
+ let bindingForAttachment = activeBinding;
760
+ let observedSiblingTitleSupport = false;
761
+ if (labelForBackfill === activeBinding.label && desiredBackfillTitle !== "" && activeBinding.displayTitle === void 0) {
762
+ const bindings = await this.#store.entries();
763
+ const freshBinding = this.#store.get(sessionKey);
764
+ if (!freshBinding || freshBinding.serverBaseUrl !== activeBinding.serverBaseUrl || freshBinding.channelId !== activeBinding.channelId || freshBinding.externalRef !== activeBinding.externalRef) return;
765
+ if (isClickClackDiscussionChannelRevoked({
766
+ runtime: this.#runtime,
767
+ serverBaseUrl: freshBinding.serverBaseUrl,
768
+ channelId: freshBinding.channelId
769
+ })) {
770
+ this.#store.delete(sessionKey);
771
+ return;
772
+ }
773
+ bindingForAttachment = freshBinding;
774
+ observedSiblingTitleSupport = bindings.some(({ binding: candidate }) => candidate.displayTitle !== void 0 && candidate.serverBaseUrl === freshBinding.serverBaseUrl && candidate.accountId === freshBinding.accountId);
775
+ }
776
+ const current = resolveDiscussionBindingAccount(this.#currentConfig(), bindingForAttachment);
777
+ if (current.state !== "active") return;
778
+ const account = current.account;
779
+ const attached = this.#refreshSessionAttachment(sessionKey, bindingForAttachment);
766
780
  if (!attached) return;
767
781
  const currentBinding = attached;
768
782
  const fallback = fallbackDiscussionLabel(sessionKey, currentBinding.agentId);
@@ -772,7 +786,7 @@ var ClickClackDiscussionService = class {
772
786
  const patch = {};
773
787
  const labelChanged = label !== currentBinding.label;
774
788
  const desiredDisplayTitle = label === fallback ? "" : truncateDiscussionDisplayTitle(label);
775
- const serverSupportsDisplayTitle = this.#store.entries().some(({ binding: candidate }) => candidate.displayTitle !== void 0 && candidate.serverBaseUrl === currentBinding.serverBaseUrl && candidate.accountId === currentBinding.accountId);
789
+ const serverSupportsDisplayTitle = currentBinding.displayTitle !== void 0 || observedSiblingTitleSupport;
776
790
  const shouldBackfillDisplayTitle = desiredDisplayTitle !== "" && currentBinding.displayTitle !== desiredDisplayTitle && serverSupportsDisplayTitle;
777
791
  if (labelChanged || shouldBackfillDisplayTitle) patch.display_title = desiredDisplayTitle;
778
792
  if (section !== currentBinding.section) patch.sidebar_section = section;
@@ -870,7 +884,7 @@ var ClickClackDiscussionService = class {
870
884
  async #ensureTimer() {
871
885
  if (this.#closed) return;
872
886
  const hasPendingOpens = (await listPendingDiscussionOpens(this.#runtime)).length > 0;
873
- const needsBindingPoll = this.#unsubscribeSessionsChanged === void 0 && this.#store.count() > 0;
887
+ const needsBindingPoll = this.#unsubscribeSessionsChanged === void 0 && await this.#store.countAsync() > 0;
874
888
  if (this.#closed || !hasPendingOpens && !needsBindingPoll) {
875
889
  if (this.#timer) {
876
890
  this.#timer.cancel();
@@ -889,7 +903,7 @@ var ClickClackDiscussionService = class {
889
903
  });
890
904
  }
891
905
  async #listReconcileSessionKeys() {
892
- return /* @__PURE__ */ new Set([...this.#store.entries().map(({ sessionKey }) => sessionKey), ...(await listPendingDiscussionOpens(this.#runtime)).map(({ sessionKey }) => sessionKey)]);
906
+ return /* @__PURE__ */ new Set([...(await this.#store.entries()).map(({ sessionKey }) => sessionKey), ...(await listPendingDiscussionOpens(this.#runtime)).map(({ sessionKey }) => sessionKey)]);
893
907
  }
894
908
  #logger() {
895
909
  return this.#runtime.logging.getChildLogger({
@@ -919,30 +933,42 @@ function blockedResult() {
919
933
  };
920
934
  }
921
935
  function isClickClackDiscussionSessionTarget(params) {
922
- const matched = getClickClackDiscussionBindingStore(params.runtime).getByDiscussionSession(params.requesterSessionKey);
923
- if (matched && matched.sessionKey === params.targetSessionKey && !isClickClackDiscussionChannelRevoked({
924
- runtime: params.runtime,
925
- serverBaseUrl: matched.binding.serverBaseUrl,
926
- channelId: matched.binding.channelId
927
- }) && bindingMatchesActiveSessionIncarnation(params.runtime, matched.sessionKey, matched.binding) && resolveDiscussionBindingAccount(params.runtime.config.current(), matched.binding).state === "active") return matched;
936
+ if (!isDiscussionSessionKey(params.requesterSessionKey)) return;
937
+ const { runtime, targetSessionKey } = params;
938
+ let binding;
939
+ try {
940
+ binding = getClickClackDiscussionBindingStore(runtime).get(targetSessionKey);
941
+ } catch {
942
+ return;
943
+ }
944
+ if (binding && discussionSessionKey({
945
+ runtime,
946
+ mainSessionKey: targetSessionKey,
947
+ ...binding
948
+ }) === params.requesterSessionKey && !isClickClackDiscussionChannelRevoked({
949
+ runtime,
950
+ serverBaseUrl: binding.serverBaseUrl,
951
+ channelId: binding.channelId
952
+ }) && bindingMatchesActiveSessionIncarnation(runtime, targetSessionKey, binding) && resolveDiscussionBindingAccount(runtime.config.current(), binding).state === "active") return {
953
+ sessionKey: targetSessionKey,
954
+ binding
955
+ };
928
956
  }
929
957
  /** Restricts a discussion side session's session tools to its attached main session. */
930
958
  function enforceClickClackDiscussionToolTarget(params) {
931
959
  const callerSessionKey = params.context.sessionKey;
932
- if (!callerSessionKey) return;
960
+ if (!callerSessionKey || !isDiscussionSessionKey(callerSessionKey)) return;
933
961
  const { toolName } = params.event;
934
962
  if (toolName !== "session_status" && !toolName.startsWith("sessions_")) return;
935
- const matched = getClickClackDiscussionBindingStore(params.runtime).getByDiscussionSession(callerSessionKey);
936
- if (!matched) return isDiscussionSessionKey(callerSessionKey) ? blockedResult() : void 0;
937
- const targetsMain = Boolean(isClickClackDiscussionSessionTarget({
938
- runtime: params.runtime,
939
- requesterSessionKey: callerSessionKey,
940
- targetSessionKey: matched.sessionKey
941
- })) && TARGETED_SESSION_TOOLS.has(toolName) && params.event.params.sessionKey === matched.sessionKey;
942
963
  const usesAlternateSendTarget = toolName === "sessions_send" && (params.event.params.label !== void 0 || params.event.params.agentId !== void 0);
943
964
  const mutatesStatus = toolName === "session_status" && params.event.params.model !== void 0;
944
965
  const selectsHistoryIncarnation = toolName === "sessions_history" && params.event.params.sessionId !== void 0;
945
- if (targetsMain && !usesAlternateSendTarget && !mutatesStatus && !selectsHistoryIncarnation) return;
966
+ const targetSessionKey = params.event.params.sessionKey;
967
+ if (TARGETED_SESSION_TOOLS.has(toolName) && typeof targetSessionKey === "string" && !usesAlternateSendTarget && !mutatesStatus && !selectsHistoryIncarnation && isClickClackDiscussionSessionTarget({
968
+ runtime: params.runtime,
969
+ requesterSessionKey: callerSessionKey,
970
+ targetSessionKey
971
+ })) return;
946
972
  return blockedResult();
947
973
  }
948
974
  //#endregion
@@ -1,4 +1,4 @@
1
- import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-DcF_7-VV.mjs";
1
+ import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-BJ5D55LV.mjs";
2
2
  import { withTimeout } from "openclaw/plugin-sdk/text-utility-runtime";
3
3
  import { createProviderOperationDeadline, readProviderJsonResponse, readResponseTextLimited, resolveProviderOperationTimeoutMs } from "openclaw/plugin-sdk/provider-http";
4
4
  import { fetchWithSsrFGuard, resolvePinnedHostnameWithPolicy, ssrfPolicyFromHttpBaseUrlAllowedHostname } from "openclaw/plugin-sdk/ssrf-runtime";
@@ -70,13 +70,8 @@ function parseClickClackSetupCodeClaim(value, expectedClaimUrl) {
70
70
  }
71
71
  /** Claims a one-time setup code without sending any existing bot credential. */
72
72
  async function claimClickClackSetupCode(params) {
73
- let parsedClaimUrl;
74
- try {
75
- parsedClaimUrl = new URL(params.claimUrl);
76
- } catch {
77
- throw new Error("ClickClack setup code claim URL must be a valid HTTP(S) endpoint.");
78
- }
79
- if (parsedClaimUrl.protocol !== "http:" && parsedClaimUrl.protocol !== "https:" || parsedClaimUrl.username || parsedClaimUrl.password || parsedClaimUrl.search || parsedClaimUrl.hash || !parsedClaimUrl.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup code claim URL must be a valid HTTP(S) endpoint.");
73
+ const parsedClaimUrl = URL.parse(params.claimUrl);
74
+ if (!parsedClaimUrl || parsedClaimUrl.protocol !== "http:" && parsedClaimUrl.protocol !== "https:" || parsedClaimUrl.username || parsedClaimUrl.password || parsedClaimUrl.search || parsedClaimUrl.hash || !parsedClaimUrl.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup code claim URL must be a valid HTTP(S) endpoint.");
80
75
  const deadline = createProviderOperationDeadline({
81
76
  label: "ClickClack setup code claim",
82
77
  timeoutMs: CLICKCLACK_SETUP_CODE_CLAIM_TIMEOUT_MS
@@ -0,0 +1,2 @@
1
+ import { t as clickClackSetupPlugin } from "./setup-plugin-api-Qi8u_lra.mjs";
2
+ export { clickClackSetupPlugin };
@@ -1,4 +1,4 @@
1
- import { l as clickClackConfigAdapter, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-DcF_7-VV.mjs";
1
+ import { l as clickClackConfigAdapter, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BJ5D55LV.mjs";
2
2
  //#region extensions/clickclack/src/channel.setup.ts
3
3
  const clickClackSetupPlugin = {
4
4
  id: "clickclack",
@@ -232,9 +232,6 @@ const clickClackConfigAdapter = {
232
232
  };
233
233
  //#endregion
234
234
  //#region extensions/clickclack/src/config-schema.ts
235
- /**
236
- * Zod-backed config schema for ClickClack channel accounts.
237
- */
238
235
  const ClickClackAccountConfigSchema = z.object({
239
236
  name: z.string().optional(),
240
237
  enabled: z.boolean().optional(),
@@ -315,13 +312,8 @@ function isClickClackSetupLoopbackHost(hostname) {
315
312
  }
316
313
  function requireClickClackSetupApiBaseUrl(value, label) {
317
314
  if (!value || value !== value.trim()) throw new Error(`ClickClack ${label} is invalid`);
318
- let parsed;
319
- try {
320
- parsed = new URL(value);
321
- } catch {
322
- throw new Error(`ClickClack ${label} is invalid`);
323
- }
324
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.hostname.endsWith(".") || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(`ClickClack ${label} is invalid`);
315
+ const parsed = URL.parse(value);
316
+ if (!parsed || parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.hostname.endsWith(".") || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(`ClickClack ${label} is invalid`);
325
317
  if (parsed.protocol === "http:" && !isClickClackSetupLoopbackHost(parsed.hostname)) throw new Error(`ClickClack ${label} must use HTTPS unless it is on loopback`);
326
318
  const pathname = parsed.pathname;
327
319
  let basePath = "";
@@ -334,13 +326,8 @@ function requireClickClackSetupApiBaseUrl(value, label) {
334
326
  return canonical;
335
327
  }
336
328
  function requireClickClackSetupClaimUrl(value) {
337
- let parsed;
338
- try {
339
- parsed = new URL(value);
340
- } catch {
341
- throw new Error("ClickClack setup URL has an invalid claim endpoint.");
342
- }
343
- if (parsed.username || parsed.password || parsed.search || parsed.hash || !parsed.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup URL has an invalid claim endpoint.");
329
+ const parsed = URL.parse(value);
330
+ if (!parsed || parsed.username || parsed.password || parsed.search || parsed.hash || !parsed.pathname.endsWith("/api/bot-setup-codes/claim")) throw new Error("ClickClack setup URL has an invalid claim endpoint.");
344
331
  const basePath = parsed.pathname.slice(0, -26);
345
332
  const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
346
333
  const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
@@ -362,15 +349,9 @@ const REQUIRED_INPUT_ERROR = "ClickClack requires --token, --base-url, and --wor
362
349
  const INVALID_BASE_URL_ERROR = "ClickClack base URL must be a valid http(s) URL.";
363
350
  const SETUP_CODE_CONFLICT_ERROR = "ClickClack --code cannot be combined with --token, --token-file, or --use-env.";
364
351
  function normalizeClickClackBaseUrl(value) {
365
- const trimmed = value?.trim();
366
- if (!trimmed) return;
367
- try {
368
- const parsed = new URL(trimmed);
369
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
370
- return parsed.toString().replace(/\/+$/, "");
371
- } catch {
372
- return;
373
- }
352
+ const parsed = URL.parse(value?.trim() ?? "");
353
+ if (!parsed || parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
354
+ return parsed.toString().replace(/\/+$/, "");
374
355
  }
375
356
  function normalizeClickClackSetupCode(value) {
376
357
  const normalized = value.trim().toUpperCase().replaceAll("-", "").replaceAll(" ", "");
@@ -389,12 +370,8 @@ function parseClickClackSetupCodeInput(params) {
389
370
  let baseUrl;
390
371
  let exactClaimUrl;
391
372
  if (/^[a-z][a-z\d+.-]*:\/\//iu.test(rawCode)) {
392
- let setupUrl;
393
- try {
394
- setupUrl = new URL(rawCode);
395
- } catch {
396
- throw new Error("ClickClack --code must be a valid HTTP(S) setup URL or a bare setup code.");
397
- }
373
+ const setupUrl = URL.parse(rawCode);
374
+ if (!setupUrl) throw new Error("ClickClack --code must be a valid HTTP(S) setup URL or a bare setup code.");
398
375
  if (setupUrl.protocol !== "http:" && setupUrl.protocol !== "https:") throw new Error("ClickClack setup codes require an HTTP(S) URL.");
399
376
  if (setupUrl.username || setupUrl.password) throw new Error("ClickClack setup URLs must not include credentials.");
400
377
  if (setupUrl.search) throw new Error("ClickClack setup URLs must not include a query.");
@@ -473,7 +450,7 @@ const clickClackSetupAdapter = {
473
450
  const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
474
451
  let claim;
475
452
  try {
476
- const { claimClickClackSetupCode } = await import("./setup-claim-Cfv5ikVH.mjs");
453
+ const { claimClickClackSetupCode } = await import("./setup-claim-C-9Pvumz.mjs");
477
454
  claim = await claimClickClackSetupCode({
478
455
  claimUrl,
479
456
  code: setup.code,
@@ -553,7 +530,7 @@ const clickClackSetupAdapter = {
553
530
  });
554
531
  },
555
532
  afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
556
- const { verifyClickClackAccountAfterSetup } = await import("./setup-verify-POQZk8SG.mjs");
533
+ const { verifyClickClackAccountAfterSetup } = await import("./setup-verify-2ruJKFPj.mjs");
557
534
  await verifyClickClackAccountAfterSetup({
558
535
  cfg,
559
536
  accountId,
@@ -735,7 +712,7 @@ const clickClackSetupWizard = {
735
712
  })
736
713
  }],
737
714
  finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
738
- const { checkClickClackSetupConnection } = await import("./setup-verify-POQZk8SG.mjs");
715
+ const { checkClickClackSetupConnection } = await import("./setup-verify-2ruJKFPj.mjs");
739
716
  const result = await checkClickClackSetupConnection({
740
717
  cfg,
741
718
  accountId,
@@ -1,5 +1,5 @@
1
- import { h as resolveClickClackAccount } from "./setup-surface-DcF_7-VV.mjs";
2
- import { a as createClickClackClient, r as resolveWorkspaceId } from "./resolve-DPtjHQHt.mjs";
1
+ import { h as resolveClickClackAccount } from "./setup-surface-BJ5D55LV.mjs";
2
+ import { a as createClickClackClient, r as resolveWorkspaceId } from "./resolve-Br3ylpSz.mjs";
3
3
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
4
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
5
5
  import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/setup";
package/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as resolveDefaultClickClackAccountId, d as DEFAULT_ACCOUNT_ID, h as resolveClickClackAccount, m as listEnabledClickClackAccounts, p as listClickClackAccountIds, s as clickClackConfigSchema } from "./.setup/setup-surface-DcF_7-VV.mjs";
2
- import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-DI3hKiSg.mjs";
3
- import { a as createClickClackClient } from "./.setup/resolve-DPtjHQHt.mjs";
1
+ import { _ as resolveDefaultClickClackAccountId, d as DEFAULT_ACCOUNT_ID, h as resolveClickClackAccount, m as listEnabledClickClackAccounts, p as listClickClackAccountIds, s as clickClackConfigSchema } from "./.setup/setup-surface-BJ5D55LV.mjs";
2
+ import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-BFuhVeiX.mjs";
3
+ import { a as createClickClackClient } from "./.setup/resolve-Br3ylpSz.mjs";
4
4
  export { DEFAULT_ACCOUNT_ID, buildClickClackTarget, clickClackConfigSchema, clickClackPlugin, createClickClackClient, getClickClackRuntime, listClickClackAccountIds, listEnabledClickClackAccounts, parseClickClackTarget, resolveClickClackAccount, resolveDefaultClickClackAccountId, setClickClackRuntime };
@@ -1,2 +1,2 @@
1
- import { t as clickClackPlugin } from "./.setup/channel-DI3hKiSg.mjs";
1
+ import { t as clickClackPlugin } from "./.setup/channel-BFuhVeiX.mjs";
2
2
  export { clickClackPlugin };
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-Dm_MQHNq.mjs";
1
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-hAV5Muz-.mjs";
2
2
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
3
3
  //#region extensions/clickclack/index.ts
4
- /**
5
- * Bundled channel entry metadata for the ClickClack plugin.
6
- */
7
4
  var clickclack_default = defineBundledChannelEntry({
8
5
  id: "clickclack",
9
6
  name: "ClickClack",
@@ -1,5 +1,5 @@
1
- import { h as resolveClickClackAccount } from "./.setup/setup-surface-DcF_7-VV.mjs";
2
- import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-DI3hKiSg.mjs";
3
- import { a as createClickClackClient } from "./.setup/resolve-DPtjHQHt.mjs";
4
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-Dm_MQHNq.mjs";
1
+ import { h as resolveClickClackAccount } from "./.setup/setup-surface-BJ5D55LV.mjs";
2
+ import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-BFuhVeiX.mjs";
3
+ import { a as createClickClackClient } from "./.setup/resolve-Br3ylpSz.mjs";
4
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-hAV5Muz-.mjs";
5
5
  export { createClickClackClient, parseClickClackTarget, registerClickClackDiscussions, resolveClickClackAccount, setClickClackRuntime };
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  importMetaUrl: import.meta.url,
6
6
  plugin: {
7
- specifier: fileURLToPath(new URL(".setup/setup-plugin-api-BXhjNJRg.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/setup-plugin-api-DpKXmzV1.mjs", import.meta.url).href),
8
8
  exportName: "clickClackSetupPlugin"
9
9
  },
10
10
  secrets: {
@@ -1,2 +1,2 @@
1
- import { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-CqfAb-CU.mjs";
1
+ import { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-Qi8u_lra.mjs";
2
2
  export { clickClackSetupPlugin };
@@ -75,11 +75,24 @@ class WebSocket extends EventEmitter {
75
75
  this._redirects = 0;
76
76
 
77
77
  if (protocols === undefined) {
78
- protocols = [];
78
+ if (!options || options.protocols === undefined) {
79
+ protocols = [];
80
+ } else if (Array.isArray(options.protocols)) {
81
+ protocols = options.protocols;
82
+ } else {
83
+ protocols = [options.protocols];
84
+ }
79
85
  } else if (!Array.isArray(protocols)) {
80
86
  if (typeof protocols === 'object' && protocols !== null) {
81
87
  options = protocols;
82
- protocols = [];
88
+
89
+ if (options.protocols === undefined) {
90
+ protocols = [];
91
+ } else if (Array.isArray(options.protocols)) {
92
+ protocols = options.protocols;
93
+ } else {
94
+ protocols = [options.protocols];
95
+ }
83
96
  } else {
84
97
  protocols = [protocols];
85
98
  }
@@ -318,7 +331,6 @@ class WebSocket extends EventEmitter {
318
331
  return;
319
332
  }
320
333
 
321
- this._readyState = WebSocket.CLOSING;
322
334
  this._sender.close(code, data, !this._isServer, (err) => {
323
335
  //
324
336
  // This error is handled by the `'error'` listener on the socket. We only
@@ -336,6 +348,7 @@ class WebSocket extends EventEmitter {
336
348
  }
337
349
  });
338
350
 
351
+ this._readyState = WebSocket.CLOSING;
339
352
  setCloseTimer(this);
340
353
  }
341
354
 
@@ -681,6 +694,7 @@ function initAsClient(websocket, address, protocols, options) {
681
694
  socketPath: undefined,
682
695
  hostname: undefined,
683
696
  protocol: undefined,
697
+ protocols: undefined,
684
698
  timeout: undefined,
685
699
  method: 'GET',
686
700
  host: undefined,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ws",
3
- "version": "8.21.3",
3
+ "version": "8.22.0",
4
4
  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
5
5
  "keywords": [
6
6
  "HyBi",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/clickclack",
3
- "version": "2026.10.1",
3
+ "version": "2026.10.5-beta.1",
4
4
  "description": "OpenClaw ClickClack channel plugin",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,11 +9,11 @@
9
9
  "./runtime-api.js": "./dist/runtime-api.js"
10
10
  },
11
11
  "dependencies": {
12
- "ws": "8.21.3",
12
+ "ws": "8.22.0",
13
13
  "zod": "4.6.5"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.10.1"
16
+ "openclaw": ">=2026.10.5-beta.1"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -139,10 +139,10 @@
139
139
  "allowInvalidConfigRecovery": true
140
140
  },
141
141
  "compat": {
142
- "pluginApi": ">=2026.10.1"
142
+ "pluginApi": ">=2026.10.5-beta.1"
143
143
  },
144
144
  "build": {
145
- "openclawVersion": "2026.10.1",
145
+ "openclawVersion": "2026.10.5-beta.1",
146
146
  "bundledDist": false
147
147
  },
148
148
  "release": {
@@ -1,2 +0,0 @@
1
- import { t as clickClackSetupPlugin } from "./setup-plugin-api-CqfAb-CU.mjs";
2
- export { clickClackSetupPlugin };