@openclaw/clickclack 2026.10.5-beta.4 → 2026.10.10-beta.2

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/README.md CHANGED
@@ -18,7 +18,7 @@ openclaw channels add clickclack --code 'https://clickclack.example.com/#XXXX-XX
18
18
 
19
19
  Split-origin and path-mounted ClickClack deployments generate an exact
20
20
  `/api/bot-setup-codes/claim#CODE` endpoint. OpenClaw validates the versioned
21
- claim response and saves its canonical API base.
21
+ claim response and saves its normalized API base URL.
22
22
 
23
23
  For manual token setup:
24
24
 
@@ -104,7 +104,7 @@ category changes remain reflected in the channel, but session archive, restore,
104
104
  reset, and deletion never archive or replace it. ClickClack owns channel archive
105
105
  and restore independently. `workspace`
106
106
  defaults to the account workspace, and `section` defaults to `Sessions`.
107
- `controlUrlBase` adds canonical `/chat/<agent>/<session-ref>` links to the
107
+ `controlUrlBase` adds standard `/chat/<agent>/<session-ref>` links to the
108
108
  OpenClaw Control UI, preserving base paths. Main sessions use `/chat/<agent>`.
109
109
 
110
110
  ClickClack-managed embed URLs explicitly advertise host-theme support. The
@@ -131,7 +131,7 @@ and scoped grant. The previous side session cannot access the new main session.
131
131
  Messages arriving through an inactive, disabled, or retargeted managed
132
132
  binding are dropped instead of falling back to the account's normal channel
133
133
  routing. Released bindings leave a durable revoked-channel marker so delayed
134
- realtime events remain fail-closed. Remote ownership is keyed by ClickClack
134
+ realtime events are still rejected. Remote ownership is keyed by ClickClack
135
135
  server and channel id, so renaming the local account cannot turn a managed
136
136
  channel into an ordinary one.
137
137
 
@@ -1,5 +1,5 @@
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-BJ5D55LV.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-BJ3jioqA.mjs";
3
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";
@@ -19,6 +19,7 @@ import { formatNormalizedAllowFromEntries, resolveAllowlistMatchByCandidates } f
19
19
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
20
20
  import { createHash, randomUUID } from "node:crypto";
21
21
  import { createAsyncLock } from "openclaw/plugin-sdk/async-lock-runtime";
22
+ import { assertPluginStateReadAvailable, preparePluginStateRead } from "openclaw/plugin-sdk/plugin-state-runtime";
22
23
  import { truncateCodePoints } from "openclaw/plugin-sdk/text-utility-runtime";
23
24
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
24
25
  import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
@@ -54,10 +55,8 @@ const storesByRuntime$2 = /* @__PURE__ */ new WeakMap();
54
55
  function withGenerationStore(runtime, run) {
55
56
  let owner = storesByRuntime$2.get(runtime);
56
57
  if (!owner) {
57
- const store = runtime.state.openKeyedStore(GENERATION_STORE_OPTIONS);
58
58
  owner = {
59
- store,
60
- ...!store.observe || !store.compareAndApply ? { native: runtime.state.openSyncKeyedStore(GENERATION_STORE_OPTIONS) } : {},
59
+ store: runtime.state.openKeyedStoreV2(GENERATION_STORE_OPTIONS),
61
60
  withLock: createAsyncLock()
62
61
  };
63
62
  storesByRuntime$2.set(runtime, owner);
@@ -66,18 +65,8 @@ function withGenerationStore(runtime, run) {
66
65
  return currentOwner.withLock(() => run(currentOwner));
67
66
  }
68
67
  function mutateGeneration(runtime, sessionKey, decide) {
69
- return withGenerationStore(runtime, async ({ store, native }) => {
70
- if (native) {
71
- const current = native.lookup(sessionKey);
72
- const next = decide(current);
73
- if (next.value !== current) {
74
- if (next.value) native.register(sessionKey, next.value);
75
- else native.delete(sessionKey);
76
- }
77
- return next.result;
78
- }
68
+ return withGenerationStore(runtime, async ({ store }) => {
79
69
  const { observe, compareAndApply } = store;
80
- if (!observe || !compareAndApply) throw new Error("ClickClack generation comparison capabilities changed");
81
70
  let observed = await observe(sessionKey);
82
71
  for (;;) {
83
72
  const next = decide(observed.value);
@@ -144,6 +133,16 @@ function recordPendingDiscussionOpen(params) {
144
133
  };
145
134
  });
146
135
  }
136
+ function getPendingDiscussionOpen(runtime, sessionKey) {
137
+ return withGenerationStore(runtime, async ({ store }) => {
138
+ const entry = await store.lookup(sessionKey);
139
+ return entry?.pending ? {
140
+ sessionKey,
141
+ generation: entry.generation,
142
+ ...entry.pending
143
+ } : void 0;
144
+ });
145
+ }
147
146
  function listPendingDiscussionOpens(runtime) {
148
147
  return withGenerationStore(runtime, async ({ store }) => (await store.entries()).flatMap((entry) => entry.value.pending ? [{
149
148
  sessionKey: entry.key,
@@ -187,18 +186,15 @@ async function readDiscussionSessionEntry(runtime, sessionKey) {
187
186
  sessionKey,
188
187
  readConsistency: "latest"
189
188
  };
190
- return runtime.agent.session.getSessionEntryAsync ? await runtime.agent.session.getSessionEntryAsync(params) : runtime.agent.session.getSessionEntry(params);
189
+ return await runtime.agent.session.getSessionEntryAsync(params);
191
190
  }
192
191
  /**
193
192
  * Refresh the replaceable session attachment without changing the durable room identity.
194
193
  * The store registers persisted state before reindexing, so a failed write leaves the
195
194
  * previous attachment authoritative in both persistence and memory.
196
195
  */
197
- function attachBindingToCurrentActiveSession(params) {
198
- const entry = params.runtime.agent.session.getSessionEntry({
199
- sessionKey: params.sessionKey,
200
- readConsistency: "latest"
201
- });
196
+ async function attachBindingToCurrentActiveSession(params) {
197
+ const entry = await readDiscussionSessionEntry(params.runtime, params.sessionKey);
202
198
  if (!entry?.sessionId || entry.archivedAt !== void 0) return;
203
199
  if (entry.sessionId === params.binding.sessionId && params.binding.detachedAt === void 0) return params.binding;
204
200
  const { detachedAt: _detachedAt, ...retained } = params.binding;
@@ -206,8 +202,10 @@ function attachBindingToCurrentActiveSession(params) {
206
202
  ...retained,
207
203
  sessionId: entry.sessionId
208
204
  };
209
- params.store.set(params.sessionKey, attached);
210
- return attached;
205
+ return await params.store.setIfCurrent(params.sessionKey, params.binding, attached, { assertCurrent: () => {
206
+ const current = params.store.get(params.sessionKey);
207
+ if (!current || current.externalRef !== params.binding.externalRef || current.serverBaseUrl !== params.binding.serverBaseUrl || current.channelId !== params.binding.channelId || !bindingMatchesActiveSessionIncarnation(params.runtime, params.sessionKey, attached)) throw new Error("ClickClack discussion session changed before attachment committed");
208
+ } }) ? attached : void 0;
211
209
  }
212
210
  const DISCUSSION_BINDINGS_NAMESPACE = "discussion-bindings";
213
211
  const MAX_DISCUSSION_BINDINGS = 1e4;
@@ -223,18 +221,18 @@ function channelKey(serverBaseUrl, channelId) {
223
221
  }
224
222
  /** SQLite-backed session/channel bindings with a process-local inbound lookup index. */
225
223
  var ClickClackDiscussionBindingStore = class {
224
+ #nativeStore;
226
225
  #store;
227
- #asyncStore;
228
226
  #sessionByChannel = /* @__PURE__ */ new Map();
229
227
  #detachedAtBySession = /* @__PURE__ */ new Map();
230
228
  #channelBySession = /* @__PURE__ */ new Map();
231
229
  #runtime;
230
+ #withMutation = createAsyncLock();
232
231
  #prepared = false;
233
232
  #preparing;
234
233
  #changedDuringPreparation = /* @__PURE__ */ new Set();
235
234
  constructor(runtime) {
236
235
  this.#runtime = runtime;
237
- this.#store = runtime.state.openSyncKeyedStore(BINDING_STORE_OPTIONS);
238
236
  }
239
237
  async prepare() {
240
238
  if (!this.#prepared) await this.entries();
@@ -242,19 +240,29 @@ var ClickClackDiscussionBindingStore = class {
242
240
  async getAsync(sessionKey) {
243
241
  return await this.#workerStore().lookup(sessionKey);
244
242
  }
243
+ async prepareRead(sessionKey) {
244
+ return await preparePluginStateRead(this.#workerStore(), sessionKey) ?? {
245
+ read: () => this.get(sessionKey),
246
+ release() {}
247
+ };
248
+ }
245
249
  get(sessionKey) {
246
- return this.#store.lookup(sessionKey);
250
+ this.#nativeStore ??= this.#runtime.state.openSyncKeyedStore(BINDING_STORE_OPTIONS);
251
+ return this.#nativeStore.lookup(sessionKey);
252
+ }
253
+ assertReadAvailable() {
254
+ assertPluginStateReadAvailable(this.#workerStore());
247
255
  }
248
256
  async hasCapacity(sessionKey) {
249
257
  return await this.getAsync(sessionKey) !== void 0 || await this.countAsync() < MAX_DISCUSSION_BINDINGS;
250
258
  }
251
- getByChannel(serverBaseUrl, channelId) {
259
+ async getByChannel(serverBaseUrl, channelId) {
252
260
  const key = channelKey(serverBaseUrl, channelId);
253
261
  const sessionKey = this.#sessionByChannel.get(key);
254
262
  if (!sessionKey) return;
255
- const binding = this.get(sessionKey);
263
+ const binding = await this.getAsync(sessionKey);
256
264
  if (!binding || channelKey(binding.serverBaseUrl, binding.channelId) !== key) {
257
- this.#sessionByChannel.delete(key);
265
+ if (this.#sessionByChannel.get(key) === sessionKey) this.#sessionByChannel.delete(key);
258
266
  return;
259
267
  }
260
268
  return {
@@ -262,17 +270,66 @@ var ClickClackDiscussionBindingStore = class {
262
270
  binding
263
271
  };
264
272
  }
265
- set(sessionKey, binding) {
266
- this.#store.register(sessionKey, binding);
273
+ async setIfCurrent(sessionKey, expected, binding, options) {
274
+ return await this.#mutateIfCurrent(sessionKey, expected, binding, options);
275
+ }
276
+ async observeCurrent(sessionKey) {
277
+ return await this.#withMutation(async () => {
278
+ const observed = await this.#workerStore().observe(sessionKey);
279
+ this.#publish(sessionKey, observed.value);
280
+ return {
281
+ binding: observed.value,
282
+ condition: {
283
+ namespace: DISCUSSION_BINDINGS_NAMESPACE,
284
+ key: sessionKey,
285
+ comparison: observed.comparison
286
+ }
287
+ };
288
+ });
289
+ }
290
+ async deleteIfCurrent(sessionKey, expected, options) {
291
+ return await this.#mutateIfCurrent(sessionKey, expected, void 0, options);
292
+ }
293
+ async #mutateIfCurrent(sessionKey, expected, next, options) {
294
+ return await this.#withMutation(async () => {
295
+ const worker = this.#workerStore();
296
+ const observed = await worker.observe(sessionKey);
297
+ if (!isDeepStrictEqual(observed.value, expected)) {
298
+ this.#publish(sessionKey, observed.value);
299
+ return false;
300
+ }
301
+ let authorityRejected = false;
302
+ try {
303
+ const result = await (options?.assertCurrent ? this.#runtime.state.openKeyedStoreV2(BINDING_STORE_OPTIONS, { assertCurrent: () => {
304
+ try {
305
+ options.assertCurrent?.();
306
+ } catch (error) {
307
+ authorityRejected = true;
308
+ throw error;
309
+ }
310
+ } }) : worker).compareAndApply(sessionKey, observed.comparison, next ? {
311
+ operation: "update",
312
+ action: "set",
313
+ value: next
314
+ } : {
315
+ operation: "delete",
316
+ action: "delete"
317
+ });
318
+ this.#publish(sessionKey, result.status === "conflict" ? result.current.value : next);
319
+ return result.status === "applied";
320
+ } catch (error) {
321
+ if (!authorityRejected) throw error;
322
+ const current = await worker.observe(sessionKey);
323
+ if (current.comparison === observed.comparison) throw error;
324
+ this.#publish(sessionKey, current.value);
325
+ return false;
326
+ }
327
+ });
328
+ }
329
+ #publish(sessionKey, binding) {
267
330
  if (!this.#prepared) this.#changedDuringPreparation.add(sessionKey);
268
331
  this.#unindex(sessionKey);
269
- this.#index(sessionKey, binding);
270
- }
271
- delete(sessionKey) {
272
- const deleted = this.#store.delete(sessionKey);
273
- if (deleted && !this.#prepared) this.#changedDuringPreparation.add(sessionKey);
274
- if (deleted) this.#unindex(sessionKey);
275
- return deleted;
332
+ if (binding) this.#index(sessionKey, binding);
276
333
  }
277
334
  async entries() {
278
335
  const load = async () => (await this.#workerStore().entries()).map((entry) => ({
@@ -291,16 +348,15 @@ var ClickClackDiscussionBindingStore = class {
291
348
  return await this.#preparing;
292
349
  }
293
350
  async countAsync() {
294
- const store = this.#workerStore();
295
- return store.count ? await store.count() : (await this.entries()).length;
351
+ return await this.#workerStore().count();
296
352
  }
297
353
  detachedCount() {
298
354
  return this.#detachedAtBySession.size;
299
355
  }
300
356
  #workerStore() {
301
- return this.#asyncStore ??= this.#runtime.state.openKeyedStore(BINDING_STORE_OPTIONS);
357
+ return this.#store ??= this.#runtime.state.openKeyedStoreV2(BINDING_STORE_OPTIONS);
302
358
  }
303
- oldestDetached() {
359
+ async oldestDetached() {
304
360
  let oldestSessionKey;
305
361
  let oldestDetachedAt = Number.POSITIVE_INFINITY;
306
362
  for (const [sessionKey, detachedAt] of this.#detachedAtBySession) if (detachedAt < oldestDetachedAt || detachedAt === oldestDetachedAt && (oldestSessionKey === void 0 || sessionKey < oldestSessionKey)) {
@@ -308,9 +364,9 @@ var ClickClackDiscussionBindingStore = class {
308
364
  oldestDetachedAt = detachedAt;
309
365
  }
310
366
  if (!oldestSessionKey) return;
311
- const binding = this.get(oldestSessionKey);
367
+ const binding = await this.getAsync(oldestSessionKey);
312
368
  if (!binding || binding.detachedAt === void 0) {
313
- this.#detachedAtBySession.delete(oldestSessionKey);
369
+ if (this.#detachedAtBySession.get(oldestSessionKey) === oldestDetachedAt) this.#detachedAtBySession.delete(oldestSessionKey);
314
370
  return this.oldestDetached();
315
371
  }
316
372
  return {
@@ -427,106 +483,172 @@ function discussionInfoForBinding(binding, account) {
427
483
  }
428
484
  //#endregion
429
485
  //#region extensions/clickclack/src/discussions/revoked-channel-store.ts
430
- const REVOKED_CHANNELS_NAMESPACE = "discussion-revoked-channels";
431
- const MAX_REVOKED_CHANNELS = 1e5;
486
+ const STORE_OPTIONS = {
487
+ namespace: "discussion-revoked-channels",
488
+ maxEntries: 1e5,
489
+ overflowPolicy: "reject-new"
490
+ };
432
491
  const storesByRuntime = /* @__PURE__ */ new WeakMap();
492
+ const nativeStoresByRuntime = /* @__PURE__ */ new WeakMap();
433
493
  function revokedChannelKey(params) {
434
494
  return [params.serverBaseUrl.replace(/\/+$/u, ""), params.channelId].join("\0");
435
495
  }
436
496
  function getStore(runtime) {
437
497
  const existing = storesByRuntime.get(runtime);
438
498
  if (existing) return existing;
439
- const created = runtime.state.openSyncKeyedStore({
440
- namespace: REVOKED_CHANNELS_NAMESPACE,
441
- maxEntries: MAX_REVOKED_CHANNELS,
442
- overflowPolicy: "reject-new"
443
- });
499
+ const created = runtime.state.openKeyedStoreV2(STORE_OPTIONS);
444
500
  storesByRuntime.set(runtime, created);
445
501
  return created;
446
502
  }
447
503
  /** Records managed ownership before its live binding is released. */
448
- function markClickClackDiscussionChannelRevoked(runtime, binding) {
504
+ async function markClickClackDiscussionChannelRevoked(runtime, sessionKey, binding, options) {
449
505
  const value = {
450
506
  accountId: binding.accountId,
451
507
  serverBaseUrl: binding.serverBaseUrl,
452
508
  channelId: binding.channelId,
453
- revokedAt: Date.now()
509
+ revokedAt: Date.now(),
510
+ binding
454
511
  };
455
- getStore(runtime).register(revokedChannelKey(value), value);
512
+ const bindings = getClickClackDiscussionBindingStore(runtime);
513
+ for (;;) {
514
+ const owner = await bindings.observeCurrent(sessionKey);
515
+ const isCurrent = isDeepStrictEqual(owner.binding, binding);
516
+ const observed = await getStore(runtime).observe(revokedChannelKey(value));
517
+ if (observed.value && !observed.value.binding) return isCurrent;
518
+ if (!isCurrent && observed.value) return false;
519
+ let authorityRejected = false;
520
+ try {
521
+ if ((await (isCurrent && options?.assertCurrent ? runtime.state.openKeyedStoreV2(STORE_OPTIONS, { assertCurrent: () => {
522
+ try {
523
+ options.assertCurrent?.();
524
+ } catch (error) {
525
+ authorityRejected = true;
526
+ throw error;
527
+ }
528
+ } }) : getStore(runtime)).compareAndApply(revokedChannelKey(value), observed.comparison, {
529
+ operation: "update",
530
+ action: "set",
531
+ value
532
+ }, { conditions: [owner.condition] })).status !== "conflict") return isCurrent;
533
+ } catch (error) {
534
+ if (authorityRejected) {
535
+ const current = await bindings.observeCurrent(sessionKey);
536
+ if (!isDeepStrictEqual(current.binding, binding)) continue;
537
+ }
538
+ throw error;
539
+ }
540
+ }
456
541
  }
457
- function markClickClackDiscussionChannelIdentityRevoked(params) {
542
+ async function markClickClackDiscussionChannelIdentityRevoked(params) {
458
543
  const value = {
459
544
  accountId: params.accountId,
460
545
  serverBaseUrl: params.serverBaseUrl.replace(/\/+$/u, ""),
461
546
  channelId: params.channelId,
462
547
  revokedAt: Date.now()
463
548
  };
464
- getStore(params.runtime).register(revokedChannelKey(value), value);
549
+ await getStore(params.runtime).register(revokedChannelKey(value), value);
465
550
  }
466
- function clearClickClackDiscussionChannelRevoked(params) {
467
- getStore(params.runtime).delete(revokedChannelKey(params));
551
+ async function clearClickClackDiscussionChannelRevoked(params) {
552
+ await getStore(params.runtime).delete(revokedChannelKey(params));
468
553
  }
469
554
  /** Distinguishes a released managed channel from a genuinely ordinary channel. */
470
555
  function isClickClackDiscussionChannelRevoked(params) {
471
- return Boolean(getStore(params.runtime).lookup(revokedChannelKey(params)));
556
+ let store = nativeStoresByRuntime.get(params.runtime);
557
+ if (!store) {
558
+ store = params.runtime.state.openSyncKeyedStore(STORE_OPTIONS);
559
+ nativeStoresByRuntime.set(params.runtime, store);
560
+ }
561
+ return revocationApplies(store.lookup(revokedChannelKey(params)), params.binding);
562
+ }
563
+ async function prepareClickClackDiscussionChannelRevocation(params) {
564
+ const facts = await preparePluginStateRead(getStore(params.runtime), revokedChannelKey(params));
565
+ return {
566
+ isRevoked: (binding) => {
567
+ if (binding && revokedChannelKey(binding) !== revokedChannelKey(params)) return true;
568
+ return facts ? revocationApplies(facts.read(), binding) : isClickClackDiscussionChannelRevoked({
569
+ ...params,
570
+ binding
571
+ });
572
+ },
573
+ release: () => facts?.release()
574
+ };
575
+ }
576
+ async function isClickClackDiscussionChannelRevokedAsync(params) {
577
+ return revocationApplies(await getStore(params.runtime).lookup(revokedChannelKey(params)), params.binding);
578
+ }
579
+ function revocationApplies(revoked, binding) {
580
+ return Boolean(revoked && (!binding || !revoked.binding || isDeepStrictEqual(revoked.binding, binding)));
472
581
  }
473
582
  //#endregion
474
583
  //#region extensions/clickclack/src/discussions/routing.ts
475
584
  async function resolveClickClackDiscussionRoute(params) {
476
585
  const store = getClickClackDiscussionBindingStore(params.runtime);
477
586
  await store.prepare();
478
- let matched = store.getByChannel(params.serverBaseUrl, params.channelId);
587
+ let matched = await store.getByChannel(params.serverBaseUrl, params.channelId);
479
588
  let pending = false;
480
589
  if (!matched) {
481
590
  pending = await hasPendingDiscussionOpenForDestination(params);
482
- matched = store.getByChannel(params.serverBaseUrl, params.channelId);
591
+ matched = await store.getByChannel(params.serverBaseUrl, params.channelId);
483
592
  }
484
- if (isClickClackDiscussionChannelRevoked(params)) return { state: "revoked" };
485
- if (!matched) return { state: pending ? "revoked" : "unbound" };
486
- if (matched.binding.accountId !== params.accountId) return { state: "revoked" };
487
- if (matched.binding.serverBaseUrl !== params.serverBaseUrl.replace(/\/+$/u, "")) return { state: "revoked" };
488
- if (resolveDiscussionBindingAccount(params.runtime.config.current(), matched.binding).state !== "active") return { state: "revoked" };
489
- let binding;
593
+ let revocation;
594
+ let bindingFacts;
490
595
  try {
491
- binding = attachBindingToCurrentActiveSession({
596
+ revocation = await prepareClickClackDiscussionChannelRevocation(params);
597
+ if (revocation.isRevoked(matched?.binding)) return { state: "revoked" };
598
+ if (!matched) return { state: pending ? "revoked" : "unbound" };
599
+ if (matched.binding.accountId !== params.accountId) return { state: "revoked" };
600
+ if (matched.binding.serverBaseUrl !== params.serverBaseUrl.replace(/\/+$/u, "")) return { state: "revoked" };
601
+ if (resolveDiscussionBindingAccount(params.runtime.config.current(), matched.binding).state !== "active") return { state: "revoked" };
602
+ bindingFacts = await store.prepareRead(matched.sessionKey);
603
+ let binding;
604
+ try {
605
+ binding = await attachBindingToCurrentActiveSession({
606
+ runtime: params.runtime,
607
+ store,
608
+ sessionKey: matched.sessionKey,
609
+ binding: matched.binding
610
+ });
611
+ } catch (error) {
612
+ params.runtime.logging.getChildLogger({
613
+ plugin: "clickclack",
614
+ feature: "discussions"
615
+ }).warn(`discussion attachment refresh failed for channel ${params.channelId}: ${String(error)}`);
616
+ return { state: "revoked" };
617
+ }
618
+ if (!binding) return { state: "revoked" };
619
+ const current = bindingFacts.read();
620
+ if (!current || current.externalRef !== binding.externalRef || current.channelId !== binding.channelId || revocation.isRevoked(current) || !bindingMatchesActiveSessionIncarnation(params.runtime, matched.sessionKey, binding) || resolveDiscussionBindingAccount(params.runtime.config.current(), binding).state !== "active") return { state: "revoked" };
621
+ const sessionKey = discussionSessionKey({
492
622
  runtime: params.runtime,
493
- store,
494
- sessionKey: matched.sessionKey,
495
- binding: matched.binding
623
+ agentId: binding.agentId,
624
+ mainSessionKey: matched.sessionKey,
625
+ sessionId: binding.sessionId,
626
+ accountId: params.accountId,
627
+ serverBaseUrl: binding.serverBaseUrl,
628
+ channelId: binding.channelId,
629
+ externalRef: binding.externalRef
496
630
  });
497
- } catch (error) {
498
- params.runtime.logging.getChildLogger({
499
- plugin: "clickclack",
500
- feature: "discussions"
501
- }).warn(`discussion attachment refresh failed for channel ${params.channelId}: ${String(error)}`);
631
+ if (!sessionKey) return { state: "revoked" };
632
+ return {
633
+ state: "active",
634
+ route: {
635
+ agentId: binding.agentId,
636
+ sessionKey,
637
+ systemPrompt: [
638
+ "You are the side agent for a ClickClack discussion attached to an OpenClaw session.",
639
+ `The main session key is ${matched.sessionKey}.`,
640
+ "Observe it with sessions_history and session_status (using changesSince for incremental checks).",
641
+ "Use sessions_send to relay or steer the main session only when the humans in this discussion ask you to.",
642
+ "These session tools are host-scoped to the attached main session; do not attempt session discovery or alternate targets."
643
+ ].join(" ")
644
+ }
645
+ };
646
+ } catch {
502
647
  return { state: "revoked" };
648
+ } finally {
649
+ bindingFacts?.release();
650
+ revocation?.release();
503
651
  }
504
- if (!binding) return { state: "revoked" };
505
- const sessionKey = discussionSessionKey({
506
- runtime: params.runtime,
507
- agentId: binding.agentId,
508
- mainSessionKey: matched.sessionKey,
509
- sessionId: binding.sessionId,
510
- accountId: params.accountId,
511
- serverBaseUrl: binding.serverBaseUrl,
512
- channelId: binding.channelId,
513
- externalRef: binding.externalRef
514
- });
515
- if (!sessionKey) return { state: "revoked" };
516
- return {
517
- state: "active",
518
- route: {
519
- agentId: binding.agentId,
520
- sessionKey,
521
- systemPrompt: [
522
- "You are the side agent for a ClickClack discussion attached to an OpenClaw session.",
523
- `The main session key is ${matched.sessionKey}.`,
524
- "Observe it with sessions_history and session_status (using changesSince for incremental checks).",
525
- "Use sessions_send to relay or steer the main session only when the humans in this discussion ask you to.",
526
- "These session tools are host-scoped to the attached main session; do not attempt session discovery or alternate targets."
527
- ].join(" ")
528
- }
529
- };
530
652
  }
531
653
  //#endregion
532
654
  //#region extensions/clickclack/src/group-policy.ts
@@ -1271,9 +1393,9 @@ function createOutboundContext(params) {
1271
1393
  }
1272
1394
  async function sendClickClackText(params) {
1273
1395
  const text = sanitizeAssistantVisibleText(params.text);
1274
- if (!text) return;
1396
+ if (!text) return { outcome: "not_sent" };
1275
1397
  const { account, client } = createOutboundContext(params);
1276
- return (await createTargetMessage({
1398
+ return { messageId: (await createTargetMessage({
1277
1399
  client,
1278
1400
  workspaceId: await resolveWorkspaceId(client, account.workspace),
1279
1401
  to: params.to,
@@ -1284,7 +1406,7 @@ async function sendClickClackText(params) {
1284
1406
  nonce: textDeliveryNonce(params),
1285
1407
  onPlatformSendDispatch: params.onPlatformSendDispatch,
1286
1408
  assertDirectAdapterHandoff: params.assertDirectAdapterHandoff
1287
- })).id;
1409
+ })).id };
1288
1410
  }
1289
1411
  async function sendClickClackMedia(params) {
1290
1412
  const nonces = mediaDeliveryNonces(params);
@@ -1559,13 +1681,6 @@ function createClickClackAgentProgressPublisher(params) {
1559
1681
  const controlFrames = queue.filter((frame) => !frame.lineId);
1560
1682
  queue.splice(0, queue.length, ...controlFrames);
1561
1683
  };
1562
- const waitForDrainWithinFinalizeGrace = async (pending) => {
1563
- let drained = false;
1564
- await raceWithTimeout(pending.then(() => {
1565
- drained = true;
1566
- }), CLICKCLACK_PROGRESS_FINALIZE_GRACE_MS, () => void 0);
1567
- return drained;
1568
- };
1569
1684
  const scheduleLineDrain = () => {
1570
1685
  if (lineDrainTimer) return;
1571
1686
  lineDrainTimer = setTimeout(() => {
@@ -1650,7 +1765,7 @@ function createClickClackAgentProgressPublisher(params) {
1650
1765
  }
1651
1766
  flushQueuedLines();
1652
1767
  enqueue({ op: "clear" });
1653
- if (!await waitForDrainWithinFinalizeGrace(drain())) discardQueuedLines();
1768
+ if (!await raceWithTimeout(drain().then(() => true), CLICKCLACK_PROGRESS_FINALIZE_GRACE_MS, () => false)) discardQueuedLines();
1654
1769
  }
1655
1770
  };
1656
1771
  }
@@ -2169,18 +2284,18 @@ const clickClackMessageAdapter = defineChannelMessageAdapter({
2169
2284
  },
2170
2285
  send: {
2171
2286
  text: async (ctx) => {
2172
- const messageId = await sendClickClackText({
2287
+ const result = await sendClickClackText({
2173
2288
  ...ctx,
2174
2289
  cfg: ctx.cfg
2175
2290
  });
2176
2291
  const threadId = ctx.threadId == null ? void 0 : String(ctx.threadId);
2177
2292
  const replyToId = ctx.replyToId ?? void 0;
2178
2293
  return {
2179
- ...messageId ? { messageId } : {},
2294
+ ...result,
2180
2295
  receipt: createMessageReceiptFromOutboundResults({
2181
- results: messageId ? [{
2296
+ results: "messageId" in result ? [{
2182
2297
  channel: CHANNEL_ID,
2183
- messageId
2298
+ messageId: result.messageId
2184
2299
  }] : [],
2185
2300
  threadId,
2186
2301
  replyToId,
@@ -2299,12 +2414,13 @@ const clickClackPlugin = createChatChannelPlugin({
2299
2414
  base: { deliveryMode: "direct" },
2300
2415
  attachedResults: {
2301
2416
  channel: CHANNEL_ID,
2302
- sendText: async (ctx) => {
2303
- return { messageId: await sendClickClackText({
2417
+ sendText: async (ctx) => ({
2418
+ messageId: "",
2419
+ ...await sendClickClackText({
2304
2420
  ...ctx,
2305
2421
  cfg: ctx.cfg
2306
- }) ?? "" };
2307
- },
2422
+ })
2423
+ }),
2308
2424
  sendMedia: async (ctx) => {
2309
2425
  const { mediaUrl, onDeliveryResult } = ctx;
2310
2426
  if (!mediaUrl) throw new Error("ClickClack media send requires mediaUrl");
@@ -2323,4 +2439,4 @@ const clickClackPlugin = createChatChannelPlugin({
2323
2439
  }
2324
2440
  });
2325
2441
  //#endregion
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 };
2442
+ export { clearPendingDiscussionOpen as A, truncateDiscussionDisplayTitle as C, getClickClackDiscussionBindingStore as D, bindingMatchesActiveSessionIncarnation as E, listPendingDiscussionOpens as M, recordPendingDiscussionOpen as N, readDiscussionSessionEntry as O, reserveDiscussionBindingGeneration as P, slugifyDiscussionLabel as S, attachBindingToCurrentActiveSession as T, discussionExternalRef as _, setClickClackRuntime as a, isDiscussionSessionKey as b, isClickClackDiscussionChannelRevokedAsync as c, prepareClickClackDiscussionChannelRevocation as d, discussionAccounts as f, discussionCredentialFingerprint as g, resolveDiscussionBindingAccount as h, getClickClackRuntime as i, getPendingDiscussionOpen as j, clearDiscussionBindingGeneration as k, markClickClackDiscussionChannelIdentityRevoked as l, normalizedServerBaseUrl as m, buildClickClackTarget as n, clearClickClackDiscussionChannelRevoked as o, discussionInfoForBinding as p, parseClickClackTarget as r, isClickClackDiscussionChannelRevoked as s, clickClackPlugin as t, markClickClackDiscussionChannelRevoked as u, discussionSessionKey as v, MAX_RETAINED_DETACHED_DISCUSSION_BINDINGS as w, resolveDiscussionLabel as x, fallbackDiscussionLabel as y };
@@ -1,7 +1,8 @@
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";
1
+ import { h as resolveClickClackAccount, p as listClickClackAccountIds } from "./setup-surface-BJ3jioqA.mjs";
2
+ import { A as clearPendingDiscussionOpen, C as truncateDiscussionDisplayTitle, D as getClickClackDiscussionBindingStore, E as bindingMatchesActiveSessionIncarnation, M as listPendingDiscussionOpens, N as recordPendingDiscussionOpen, O as readDiscussionSessionEntry, P as reserveDiscussionBindingGeneration, S as slugifyDiscussionLabel, T as attachBindingToCurrentActiveSession, _ as discussionExternalRef, b as isDiscussionSessionKey, c as isClickClackDiscussionChannelRevokedAsync, d as prepareClickClackDiscussionChannelRevocation, f as discussionAccounts, g as discussionCredentialFingerprint, h as resolveDiscussionBindingAccount, j as getPendingDiscussionOpen, k as clearDiscussionBindingGeneration, l as markClickClackDiscussionChannelIdentityRevoked, m as normalizedServerBaseUrl, o as clearClickClackDiscussionChannelRevoked, p as discussionInfoForBinding, s as isClickClackDiscussionChannelRevoked, u as markClickClackDiscussionChannelRevoked, v as discussionSessionKey, x as resolveDiscussionLabel, y as fallbackDiscussionLabel } from "./channel-DRcZ_twH.mjs";
3
3
  import { a as createClickClackClient, i as ClickClackHttpError, o as isClickClackChannelNameConflict, t as findClickClackWorkspace } from "./resolve-Br3ylpSz.mjs";
4
4
  import "../api.js";
5
+ import { isDeepStrictEqual } from "node:util";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { createAsyncLock } from "openclaw/plugin-sdk/async-lock-runtime";
7
8
  import { buildControlUiSessionPath, registerSessionDiscussionProvider } from "openclaw/plugin-sdk/session-discussion";
@@ -19,47 +20,54 @@ var DetachedDiscussionBindingRetention = class {
19
20
  this.#store = options.store;
20
21
  this.#maxRetained = options.maxRetained;
21
22
  }
22
- mark(sessionKey, binding) {
23
- const current = this.#store.get(sessionKey);
24
- if (!current || !this.#sameRoom(current, binding)) return;
25
- if (current.detachedAt === void 0) this.#store.set(sessionKey, {
23
+ async mark(sessionKey, binding) {
24
+ const current = await this.#store.getAsync(sessionKey);
25
+ if (!current || !isDeepStrictEqual(current, binding)) return;
26
+ if (current.detachedAt === void 0) await this.#store.setIfCurrent(sessionKey, current, {
26
27
  ...current,
27
28
  detachedAt: Date.now()
28
- });
29
- while (this.#store.detachedCount() > this.#maxRetained) if (!this.#pruneOldest()) throw new Error("ClickClack detached discussion binding retention could not be reduced");
29
+ }, { assertCurrent: () => this.#assertDetached(sessionKey, current) });
30
+ while (this.#store.detachedCount() > this.#maxRetained) if (!await this.#pruneOldest()) throw new Error("ClickClack detached discussion binding retention could not be reduced");
30
31
  }
31
- clear(sessionKey, binding) {
32
- const current = this.#store.get(sessionKey);
33
- if (!current || !this.#sameRoom(current, binding)) return;
32
+ async clear(sessionKey, binding) {
33
+ const current = await this.#store.getAsync(sessionKey);
34
+ if (!current || !isDeepStrictEqual(current, binding)) return;
34
35
  if (current.detachedAt === void 0) return current;
35
36
  const { detachedAt: _detachedAt, ...retained } = current;
36
- this.#store.set(sessionKey, retained);
37
- return retained;
37
+ return await this.#store.setIfCurrent(sessionKey, current, retained, { assertCurrent: () => this.#assertRoomCurrent(sessionKey, current) }) ? retained : void 0;
38
38
  }
39
39
  async ensureCapacity(sessionKey) {
40
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
+ while (!await this.#store.hasCapacity(sessionKey)) if (!await this.#pruneOldest()) throw new Error("ClickClack discussion binding capacity is exhausted");
42
42
  }
43
- #pruneOldest() {
43
+ async #pruneOldest() {
44
44
  for (;;) {
45
- const oldest = this.#store.oldestDetached();
45
+ const oldest = await this.#store.oldestDetached();
46
46
  if (!oldest) return false;
47
47
  const current = oldest.binding;
48
- if (this.#runtime.agent.session.getSessionEntry({
49
- sessionKey: oldest.sessionKey,
50
- readConsistency: "latest"
51
- })) {
52
- this.clear(oldest.sessionKey, current);
48
+ if (await readDiscussionSessionEntry(this.#runtime, oldest.sessionKey)) {
49
+ await this.clear(oldest.sessionKey, current);
53
50
  continue;
54
51
  }
55
- markClickClackDiscussionChannelRevoked(this.#runtime, current);
56
- this.#store.delete(oldest.sessionKey);
57
- return true;
52
+ const authority = { assertCurrent: () => this.#assertDetached(oldest.sessionKey, current) };
53
+ if (!await markClickClackDiscussionChannelRevoked(this.#runtime, oldest.sessionKey, current, authority)) continue;
54
+ if (await this.#store.deleteIfCurrent(oldest.sessionKey, current, authority)) return true;
58
55
  }
59
56
  }
60
57
  #sameRoom(left, right) {
61
58
  return left.serverBaseUrl === right.serverBaseUrl && left.channelId === right.channelId && left.externalRef === right.externalRef;
62
59
  }
60
+ #assertRoomCurrent(sessionKey, expected) {
61
+ const current = this.#store.get(sessionKey);
62
+ if (!current || !this.#sameRoom(current, expected)) throw new Error("ClickClack discussion binding changed before retention committed");
63
+ }
64
+ #assertDetached(sessionKey, expected) {
65
+ this.#assertRoomCurrent(sessionKey, expected);
66
+ if (this.#runtime.agent.session.getSessionEntry({
67
+ sessionKey,
68
+ readConsistency: "latest"
69
+ })) throw new Error("ClickClack discussion session returned before retention committed");
70
+ }
63
71
  };
64
72
  //#endregion
65
73
  //#region extensions/clickclack/src/discussions/control-session-url.ts
@@ -99,7 +107,7 @@ const INSTALLATION_NAMESPACE = "discussion-installation";
99
107
  const INSTALLATION_KEY = "current";
100
108
  /** Returns the durable installation namespace used in server-visible ownership refs. */
101
109
  async function getClickClackDiscussionInstallationId(runtime) {
102
- const store = runtime.state.openKeyedStore({
110
+ const store = runtime.state.openKeyedStoreV2({
103
111
  namespace: INSTALLATION_NAMESPACE,
104
112
  maxEntries: 1,
105
113
  overflowPolicy: "reject-new"
@@ -258,10 +266,10 @@ async function openClickClackDiscussionBinding(params) {
258
266
  serverBaseUrl,
259
267
  channelId
260
268
  });
261
- const unresolved = (await listPendingDiscussionOpens(runtime)).find((pending) => pending.sessionKey === sessionKey);
269
+ const unresolved = await getPendingDiscussionOpen(runtime, sessionKey);
262
270
  if (unresolved && (unresolved.accountId !== account.accountId || unresolved.credentialFingerprint !== credentialFingerprint || unresolved.serverBaseUrl !== serverBaseUrl || unresolved.workspaceId !== workspace.id)) {
263
271
  await params.reconcilePendingOpen(unresolved);
264
- if ((await listPendingDiscussionOpens(runtime)).some((pending) => pending.sessionKey === sessionKey)) throw new Error("A previous ClickClack discussion open is still unresolved; restore its credential and retry");
272
+ if (await getPendingDiscussionOpen(runtime, sessionKey)) throw new Error("A previous ClickClack discussion open is still unresolved; restore its credential and retry");
265
273
  }
266
274
  const config = runtime.config.current();
267
275
  const agentId = resolveSessionAgentIdStrict({
@@ -346,14 +354,15 @@ async function openClickClackDiscussionBinding(params) {
346
354
  assertCurrentAuthority();
347
355
  try {
348
356
  if (adopted) {
349
- revokeChannel(adopted.id);
357
+ await revokeChannel(adopted.id);
358
+ assertCurrentAuthority();
350
359
  resolved = await client.updateChannel(adopted.id, managedFields);
351
360
  } else {
352
361
  resolved = await client.createChannel(workspace.id, {
353
362
  ...managedFields,
354
363
  kind: "public"
355
364
  });
356
- revokeChannel(resolved.id);
365
+ await revokeChannel(resolved.id);
357
366
  }
358
367
  break;
359
368
  } catch (error) {
@@ -369,7 +378,7 @@ async function openClickClackDiscussionBinding(params) {
369
378
  const recovered = relisted.find((candidate) => candidate.external_managed === true && candidate.external_ref === externalRef);
370
379
  if (recovered) {
371
380
  adopted = recovered;
372
- revokeChannel(recovered.id);
381
+ await revokeChannel(recovered.id);
373
382
  assertCurrentAuthority();
374
383
  resolved = await client.updateChannel(recovered.id, managedFields);
375
384
  break;
@@ -452,7 +461,13 @@ async function openClickClackDiscussionBinding(params) {
452
461
  };
453
462
  try {
454
463
  nextBinding.sessionId = assertCurrentAuthority().sessionId;
455
- store.set(sessionKey, nextBinding);
464
+ if (!await store.setIfCurrent(sessionKey, void 0, nextBinding, { assertCurrent: () => {
465
+ if (assertCurrentAuthority().sessionId !== nextBinding.sessionId) throw new Error("ClickClack discussion session changed before binding committed");
466
+ } })) {
467
+ await clearPendingDiscussionOpen(generationScope);
468
+ params.warn(`superseded discussion channel remains quarantined: ${channel.id}`);
469
+ return;
470
+ }
456
471
  } catch (error) {
457
472
  await clearPendingDiscussionOpen(generationScope);
458
473
  params.warn(`unbound discussion channel remains quarantined: ${channel.id}`);
@@ -490,7 +505,7 @@ async function reconcilePendingDiscussionOpen(params) {
490
505
  assertManagedChannelListContract(channels);
491
506
  const channel = channels.find((candidate) => candidate.external_managed === true && candidate.external_ref === pending.externalRef);
492
507
  if (channel) {
493
- markClickClackDiscussionChannelIdentityRevoked({
508
+ await markClickClackDiscussionChannelIdentityRevoked({
494
509
  runtime: params.runtime,
495
510
  accountId: pending.accountId,
496
511
  serverBaseUrl: pending.serverBaseUrl,
@@ -567,7 +582,7 @@ var ClickClackDiscussionService = class {
567
582
  });
568
583
  this.#unsubscribeSessionsChanged = gatewayEvents?.onSessionsChanged((event) => {
569
584
  this.#withOperation(async () => {
570
- if (await this.#store.getAsync(event.sessionKey) || (await listPendingDiscussionOpens(this.#runtime)).some((pending) => pending.sessionKey === event.sessionKey)) {
585
+ if (await this.#store.getAsync(event.sessionKey) || await getPendingDiscussionOpen(this.#runtime, event.sessionKey)) {
571
586
  if (activation === this.#activation) this.#reconcileScheduler?.schedule(event.sessionKey);
572
587
  }
573
588
  }).catch((error) => {
@@ -593,7 +608,7 @@ var ClickClackDiscussionService = class {
593
608
  if (existing) {
594
609
  const resolved = await this.#resolveBindingForUse(existing);
595
610
  if (resolved.state === "retargeted") {
596
- this.#revokeAndDeleteBinding(sessionKey, existing);
611
+ await this.#revokeAndDeleteBinding(sessionKey, existing);
597
612
  return { state: "available" };
598
613
  }
599
614
  if (resolved.state === "stale") {
@@ -601,7 +616,6 @@ var ClickClackDiscussionService = class {
601
616
  return { state: "available" };
602
617
  }
603
618
  if (resolved.state !== "active") return { state: "none" };
604
- await this.#finalizePendingBinding(sessionKey, existing);
605
619
  await this.#reconcileBinding(sessionKey, existing, resolved.account);
606
620
  const current = await this.#store.getAsync(sessionKey);
607
621
  if (!current) return { state: this.hasEnabledAccount() ? "available" : "none" };
@@ -625,10 +639,11 @@ var ClickClackDiscussionService = class {
625
639
  const existing = await this.#store.getAsync(sessionKey);
626
640
  if (existing) {
627
641
  const resolved = await this.#resolveBindingForUse(existing);
628
- if (resolved.state === "retargeted") this.#revokeAndDeleteBinding(sessionKey, existing);
629
- else if (resolved.state === "stale") await this.#releaseStaleBinding(sessionKey, existing);
630
- else if (resolved.state === "active") {
631
- await this.#finalizePendingBinding(sessionKey, existing);
642
+ if (resolved.state === "retargeted") {
643
+ if (!await this.#revokeAndDeleteBinding(sessionKey, existing)) return { state: "available" };
644
+ } else if (resolved.state === "stale") {
645
+ if (!await this.#releaseStaleBinding(sessionKey, existing)) return { state: "available" };
646
+ } else if (resolved.state === "active") {
632
647
  await this.#reconcileBinding(sessionKey, existing, resolved.account);
633
648
  const current = await this.#store.getAsync(sessionKey);
634
649
  if (current) return discussionInfoForBinding(current, resolved.account);
@@ -666,7 +681,7 @@ var ClickClackDiscussionService = class {
666
681
  await this.#store.prepare();
667
682
  const binding = await this.#store.getAsync(sessionKey);
668
683
  if (binding) await this.#reconcileBinding(sessionKey, binding);
669
- const pending = (await listPendingDiscussionOpens(this.#runtime)).find((candidate) => candidate.sessionKey === sessionKey);
684
+ const pending = await getPendingDiscussionOpen(this.#runtime, sessionKey);
670
685
  if (pending) await this.#reconcilePendingOpen(pending);
671
686
  });
672
687
  } finally {
@@ -699,12 +714,13 @@ var ClickClackDiscussionService = class {
699
714
  if (!binding) return { text: "No discussion is bound to this session." };
700
715
  const resolved = await this.#resolveBindingForUse(binding);
701
716
  if (resolved.state !== "active") return { text: "No discussion is bound to this session." };
702
- const attached = this.#refreshSessionAttachment(sessionKey, binding);
717
+ const attached = await this.#refreshSessionAttachment(sessionKey, binding);
703
718
  if (!attached) return { text: "No discussion is bound to this session." };
704
- if (isClickClackDiscussionChannelRevoked({
719
+ if (!bindingMatchesActiveSessionIncarnation(this.#runtime, sessionKey, attached) || resolveDiscussionBindingAccount(this.#currentConfig(), attached).state !== "active" || isClickClackDiscussionChannelRevoked({
705
720
  runtime: this.#runtime,
706
721
  serverBaseUrl: binding.serverBaseUrl,
707
- channelId: binding.channelId
722
+ channelId: binding.channelId,
723
+ binding: attached
708
724
  })) return { text: "No discussion is bound to this session." };
709
725
  return {
710
726
  binding: attached,
@@ -724,27 +740,25 @@ var ClickClackDiscussionService = class {
724
740
  }
725
741
  async #reconcileBinding(sessionKey, binding, resolvedAccount) {
726
742
  await this.#finalizePendingBinding(sessionKey, binding);
727
- if (isClickClackDiscussionChannelRevoked({
743
+ if (await isClickClackDiscussionChannelRevokedAsync({
728
744
  runtime: this.#runtime,
729
745
  serverBaseUrl: binding.serverBaseUrl,
730
- channelId: binding.channelId
746
+ channelId: binding.channelId,
747
+ binding
731
748
  })) {
732
- this.#store.delete(sessionKey);
749
+ await this.#store.deleteIfCurrent(sessionKey, binding);
733
750
  return;
734
751
  }
735
- const entry = this.#runtime.agent.session.getSessionEntry({
736
- sessionKey,
737
- readConsistency: "latest"
738
- });
752
+ const entry = await readDiscussionSessionEntry(this.#runtime, sessionKey);
739
753
  if (!entry) {
740
- this.#detachedBindings.mark(sessionKey, binding);
754
+ await this.#detachedBindings.mark(sessionKey, binding);
741
755
  return;
742
756
  }
743
- const activeBinding = this.#detachedBindings.clear(sessionKey, binding);
757
+ const activeBinding = await this.#detachedBindings.clear(sessionKey, binding);
744
758
  if (!activeBinding) return;
745
759
  const resolved = resolvedAccount ? resolveDiscussionBindingAccount(this.#currentConfig(), activeBinding) : await this.#resolveBindingForUse(activeBinding);
746
760
  if (resolved.state === "retargeted") {
747
- this.#revokeAndDeleteBinding(sessionKey, activeBinding);
761
+ await this.#revokeAndDeleteBinding(sessionKey, activeBinding);
748
762
  return;
749
763
  }
750
764
  if (resolved.state === "stale") {
@@ -760,14 +774,15 @@ var ClickClackDiscussionService = class {
760
774
  let observedSiblingTitleSupport = false;
761
775
  if (labelForBackfill === activeBinding.label && desiredBackfillTitle !== "" && activeBinding.displayTitle === void 0) {
762
776
  const bindings = await this.#store.entries();
763
- const freshBinding = this.#store.get(sessionKey);
777
+ const freshBinding = await this.#store.getAsync(sessionKey);
764
778
  if (!freshBinding || freshBinding.serverBaseUrl !== activeBinding.serverBaseUrl || freshBinding.channelId !== activeBinding.channelId || freshBinding.externalRef !== activeBinding.externalRef) return;
765
- if (isClickClackDiscussionChannelRevoked({
779
+ if (await isClickClackDiscussionChannelRevokedAsync({
766
780
  runtime: this.#runtime,
767
781
  serverBaseUrl: freshBinding.serverBaseUrl,
768
- channelId: freshBinding.channelId
782
+ channelId: freshBinding.channelId,
783
+ binding: freshBinding
769
784
  })) {
770
- this.#store.delete(sessionKey);
785
+ await this.#store.deleteIfCurrent(sessionKey, freshBinding);
771
786
  return;
772
787
  }
773
788
  bindingForAttachment = freshBinding;
@@ -776,8 +791,8 @@ var ClickClackDiscussionService = class {
776
791
  const current = resolveDiscussionBindingAccount(this.#currentConfig(), bindingForAttachment);
777
792
  if (current.state !== "active") return;
778
793
  const account = current.account;
779
- const attached = this.#refreshSessionAttachment(sessionKey, bindingForAttachment);
780
- if (!attached) return;
794
+ const attached = await this.#refreshSessionAttachment(sessionKey, bindingForAttachment);
795
+ if (!attached || attached.sessionId !== entry.sessionId) return;
781
796
  const currentBinding = attached;
782
797
  const fallback = fallbackDiscussionLabel(sessionKey, currentBinding.agentId);
783
798
  const label = resolveDiscussionLabel(entry, sessionKey, currentBinding.agentId);
@@ -793,6 +808,15 @@ var ClickClackDiscussionService = class {
793
808
  if (externalUrl !== currentBinding.externalUrl) patch.external_url = externalUrl;
794
809
  if (Object.keys(patch).length === 0 && !labelChanged) return;
795
810
  const client = this.#clientFactory(account);
811
+ const assertCurrentAuthority = () => {
812
+ const bound = this.#store.get(sessionKey);
813
+ if (!bound || bound.externalRef !== currentBinding.externalRef || bound.serverBaseUrl !== currentBinding.serverBaseUrl || bound.channelId !== currentBinding.channelId || !bindingMatchesActiveSessionIncarnation(this.#runtime, sessionKey, currentBinding) || resolveDiscussionBindingAccount(this.#currentConfig(), currentBinding).state !== "active" || isClickClackDiscussionChannelRevoked({
814
+ runtime: this.#runtime,
815
+ serverBaseUrl: currentBinding.serverBaseUrl,
816
+ channelId: currentBinding.channelId,
817
+ binding: bound
818
+ })) throw new Error("ClickClack discussion authority changed before channel update");
819
+ };
796
820
  let updated;
797
821
  if (labelChanged) updated = await this.#withChannelMutationLock(() => renameDiscussionChannel({
798
822
  client,
@@ -801,27 +825,27 @@ var ClickClackDiscussionService = class {
801
825
  label,
802
826
  sessionKey,
803
827
  agentId: currentBinding.agentId,
804
- patch
828
+ patch,
829
+ assertCurrentAuthority
805
830
  }));
806
831
  else {
832
+ assertCurrentAuthority();
807
833
  updated = await client.updateChannel(currentBinding.channelId, patch);
808
834
  assertChannelPatch(updated, patch);
809
835
  }
810
- const latestBinding = this.#store.get(sessionKey);
811
- if (!latestBinding || latestBinding.serverBaseUrl !== currentBinding.serverBaseUrl || latestBinding.channelId !== currentBinding.channelId || latestBinding.externalRef !== currentBinding.externalRef) return;
812
836
  const nextBinding = {
813
- ...latestBinding,
837
+ ...currentBinding,
814
838
  externalUrl,
815
839
  label,
816
840
  section,
817
841
  ...updated.display_title !== void 0 ? { displayTitle: updated.display_title } : {}
818
842
  };
819
843
  if (updated.display_title === void 0) delete nextBinding.displayTitle;
820
- this.#store.set(sessionKey, nextBinding);
844
+ await this.#store.setIfCurrent(sessionKey, currentBinding, nextBinding, { assertCurrent: assertCurrentAuthority });
821
845
  }
822
- #refreshSessionAttachment(sessionKey, binding) {
846
+ async #refreshSessionAttachment(sessionKey, binding) {
823
847
  try {
824
- return attachBindingToCurrentActiveSession({
848
+ return await attachBindingToCurrentActiveSession({
825
849
  runtime: this.#runtime,
826
850
  store: this.#store,
827
851
  sessionKey,
@@ -844,20 +868,22 @@ var ClickClackDiscussionService = class {
844
868
  });
845
869
  }
846
870
  async #releaseStaleBinding(sessionKey, binding) {
847
- await clearDiscussionBindingGeneration({
871
+ const pending = await getPendingDiscussionOpen(this.#runtime, sessionKey);
872
+ if (pending?.externalRef === binding.externalRef) await clearDiscussionBindingGeneration({
848
873
  runtime: this.#runtime,
849
- sessionKey
874
+ sessionKey,
875
+ expectedGeneration: pending.generation
850
876
  });
851
- this.#revokeAndDeleteBinding(sessionKey, binding);
877
+ return await this.#revokeAndDeleteBinding(sessionKey, binding);
852
878
  }
853
- #revokeAndDeleteBinding(sessionKey, binding) {
854
- markClickClackDiscussionChannelRevoked(this.#runtime, binding);
855
- this.#store.delete(sessionKey);
879
+ async #revokeAndDeleteBinding(sessionKey, binding) {
880
+ if (!await markClickClackDiscussionChannelRevoked(this.#runtime, sessionKey, binding)) return false;
881
+ return await this.#store.deleteIfCurrent(sessionKey, binding);
856
882
  }
857
883
  async #finalizePendingBinding(sessionKey, binding) {
858
- const pending = (await listPendingDiscussionOpens(this.#runtime)).find((candidate) => candidate.sessionKey === sessionKey && candidate.externalRef === binding.externalRef);
859
- if (pending) {
860
- clearClickClackDiscussionChannelRevoked({
884
+ const pending = await getPendingDiscussionOpen(this.#runtime, sessionKey);
885
+ if (pending?.externalRef === binding.externalRef) {
886
+ await clearClickClackDiscussionChannelRevoked({
861
887
  runtime: this.#runtime,
862
888
  serverBaseUrl: binding.serverBaseUrl,
863
889
  channelId: binding.channelId
@@ -937,25 +963,52 @@ function isClickClackDiscussionSessionTarget(params) {
937
963
  const { runtime, targetSessionKey } = params;
938
964
  let binding;
939
965
  try {
940
- binding = getClickClackDiscussionBindingStore(runtime).get(targetSessionKey);
966
+ const store = getClickClackDiscussionBindingStore(runtime);
967
+ store.assertReadAvailable();
968
+ binding = store.get(targetSessionKey);
941
969
  } catch {
942
970
  return;
943
971
  }
972
+ return currentDiscussionTarget(params, binding, (current) => isClickClackDiscussionChannelRevoked({
973
+ runtime,
974
+ serverBaseUrl: current.serverBaseUrl,
975
+ channelId: current.channelId,
976
+ binding: current
977
+ }));
978
+ }
979
+ function currentDiscussionTarget(params, binding, isRevoked) {
980
+ const { runtime, targetSessionKey } = params;
944
981
  if (binding && discussionSessionKey({
945
982
  runtime,
946
983
  mainSessionKey: targetSessionKey,
947
984
  ...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 {
985
+ }) === params.requesterSessionKey && !isRevoked(binding) && bindingMatchesActiveSessionIncarnation(runtime, targetSessionKey, binding) && resolveDiscussionBindingAccount(runtime.config.current(), binding).state === "active") return {
953
986
  sessionKey: targetSessionKey,
954
987
  binding
955
988
  };
956
989
  }
990
+ async function resolveClickClackDiscussionSessionTarget(params) {
991
+ if (!isDiscussionSessionKey(params.requesterSessionKey)) return;
992
+ let facts;
993
+ let revocation;
994
+ try {
995
+ facts = await getClickClackDiscussionBindingStore(params.runtime).prepareRead(params.targetSessionKey);
996
+ const binding = facts.read();
997
+ if (!binding) return;
998
+ revocation = await prepareClickClackDiscussionChannelRevocation({
999
+ runtime: params.runtime,
1000
+ ...binding
1001
+ });
1002
+ return currentDiscussionTarget(params, facts.read(), revocation.isRevoked);
1003
+ } catch {
1004
+ return;
1005
+ } finally {
1006
+ revocation?.release();
1007
+ facts?.release();
1008
+ }
1009
+ }
957
1010
  /** Restricts a discussion side session's session tools to its attached main session. */
958
- function enforceClickClackDiscussionToolTarget(params) {
1011
+ async function enforceClickClackDiscussionToolTarget(params) {
959
1012
  const callerSessionKey = params.context.sessionKey;
960
1013
  if (!callerSessionKey || !isDiscussionSessionKey(callerSessionKey)) return;
961
1014
  const { toolName } = params.event;
@@ -964,7 +1017,7 @@ function enforceClickClackDiscussionToolTarget(params) {
964
1017
  const mutatesStatus = toolName === "session_status" && params.event.params.model !== void 0;
965
1018
  const selectsHistoryIncarnation = toolName === "sessions_history" && params.event.params.sessionId !== void 0;
966
1019
  const targetSessionKey = params.event.params.sessionKey;
967
- if (TARGETED_SESSION_TOOLS.has(toolName) && typeof targetSessionKey === "string" && !usesAlternateSendTarget && !mutatesStatus && !selectsHistoryIncarnation && isClickClackDiscussionSessionTarget({
1020
+ if (TARGETED_SESSION_TOOLS.has(toolName) && typeof targetSessionKey === "string" && !usesAlternateSendTarget && !mutatesStatus && !selectsHistoryIncarnation && await resolveClickClackDiscussionSessionTarget({
968
1021
  runtime: params.runtime,
969
1022
  requesterSessionKey: callerSessionKey,
970
1023
  targetSessionKey
@@ -1033,7 +1086,14 @@ function registerClickClackDiscussions(api) {
1033
1086
  targetSessionKey
1034
1087
  });
1035
1088
  return target ? { expectedSessionId: target.binding.sessionId } : void 0;
1036
- });
1089
+ }, { async resolveAsync({ requesterSessionKey, targetSessionKey }) {
1090
+ const target = await resolveClickClackDiscussionSessionTarget({
1091
+ runtime: api.runtime,
1092
+ requesterSessionKey,
1093
+ targetSessionKey
1094
+ });
1095
+ return target ? { expectedSessionId: target.binding.sessionId } : void 0;
1096
+ } });
1037
1097
  registerSessionDiscussionProvider(service.provider);
1038
1098
  api.lifecycle.registerRuntimeLifecycle({
1039
1099
  id: "clickclack-discussions",
@@ -0,0 +1,2 @@
1
+ export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-CF7TKtkU.mjs";
2
+ import "../secret-contract-api.js";
@@ -1,4 +1,4 @@
1
- import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-BJ5D55LV.mjs";
1
+ import { a as isClickClackSetupLoopbackHost, i as buildClickClackSetupClaimUrl, o as requireClickClackSetupApiBaseUrl } from "./setup-surface-BJ3jioqA.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";
@@ -0,0 +1 @@
1
+ export { t as clickClackSetupPlugin } from "./setup-plugin-api-Ccxm2ydn.mjs";
@@ -1,4 +1,4 @@
1
- import { l as clickClackConfigAdapter, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BJ5D55LV.mjs";
1
+ import { l as clickClackConfigAdapter, n as clickClackSetupContract, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-BJ3jioqA.mjs";
2
2
  //#region extensions/clickclack/src/channel.setup.ts
3
3
  const clickClackSetupPlugin = {
4
4
  id: "clickclack",
@@ -195,7 +195,7 @@ function resolveClickClackAccount(params) {
195
195
  mentionPatterns: merged.mentionPatterns ?? [],
196
196
  allowBots: merged.allowBots ?? false,
197
197
  botLoopProtection: merged.botLoopProtection,
198
- groups: mergeClickClackGroups(merged.groups),
198
+ groups: merged.groups ?? {},
199
199
  config: {
200
200
  ...merged,
201
201
  allowFrom: merged.allowFrom ?? ["*"]
@@ -322,7 +322,7 @@ function requireClickClackSetupApiBaseUrl(value, label) {
322
322
  basePath = pathname;
323
323
  }
324
324
  const canonical = parsed.origin + basePath;
325
- if (value !== canonical) throw new Error(`ClickClack ${label} is not canonical`);
325
+ if (value !== canonical) throw new Error(`ClickClack ${label} is not in the expected URL format`);
326
326
  return canonical;
327
327
  }
328
328
  function requireClickClackSetupClaimUrl(value) {
@@ -331,7 +331,7 @@ function requireClickClackSetupClaimUrl(value) {
331
331
  const basePath = parsed.pathname.slice(0, -26);
332
332
  const apiBaseUrl = requireClickClackSetupApiBaseUrl(parsed.origin + basePath, "setup URL API base");
333
333
  const claimUrl = apiBaseUrl + CLICKCLACK_SETUP_CODE_CLAIM_PATH;
334
- if (value !== claimUrl) throw new Error("ClickClack setup URL has a non-canonical claim endpoint.");
334
+ if (value !== claimUrl) throw new Error("ClickClack setup URL has a claim endpoint in an unexpected URL format.");
335
335
  return {
336
336
  claimUrl,
337
337
  apiBaseUrl
@@ -450,7 +450,7 @@ const clickClackSetupAdapter = {
450
450
  const claimUrl = setup.exactClaimUrl && !privateApiBaseUrl ? setup.exactClaimUrl : buildClickClackSetupClaimUrl(privateApiBaseUrl ?? setup.baseUrl);
451
451
  let claim;
452
452
  try {
453
- const { claimClickClackSetupCode } = await import("./setup-claim-C-9Pvumz.mjs");
453
+ const { claimClickClackSetupCode } = await import("./setup-claim-BPuQuMKW.mjs");
454
454
  claim = await claimClickClackSetupCode({
455
455
  claimUrl,
456
456
  code: setup.code,
@@ -530,7 +530,7 @@ const clickClackSetupAdapter = {
530
530
  });
531
531
  },
532
532
  afterAccountConfigWritten: async ({ cfg, accountId, runtime }) => {
533
- const { verifyClickClackAccountAfterSetup } = await import("./setup-verify-2ruJKFPj.mjs");
533
+ const { verifyClickClackAccountAfterSetup } = await import("./setup-verify-DGsYtVh9.mjs");
534
534
  await verifyClickClackAccountAfterSetup({
535
535
  cfg,
536
536
  accountId,
@@ -712,7 +712,7 @@ const clickClackSetupWizard = {
712
712
  })
713
713
  }],
714
714
  finalize: async ({ cfg, accountId, credentialValues, prompter }) => {
715
- const { checkClickClackSetupConnection } = await import("./setup-verify-2ruJKFPj.mjs");
715
+ const { checkClickClackSetupConnection } = await import("./setup-verify-DGsYtVh9.mjs");
716
716
  const result = await checkClickClackSetupConnection({
717
717
  cfg,
718
718
  accountId,
@@ -1,5 +1,5 @@
1
- import { h as resolveClickClackAccount } from "./setup-surface-BJ5D55LV.mjs";
2
- import { a as createClickClackClient, r as resolveWorkspaceId } from "./resolve-Br3ylpSz.mjs";
1
+ import { h as resolveClickClackAccount } from "./setup-surface-BJ3jioqA.mjs";
2
+ import { a as createClickClackClient, t as findClickClackWorkspace } 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";
@@ -32,8 +32,8 @@ async function checkClickClackSetupConnection(params) {
32
32
  token
33
33
  });
34
34
  const me = await client.me();
35
- const workspaceId = await resolveWorkspaceId(client, account.workspace);
36
- const workspace = (await client.workspaces()).find((candidate) => candidate.id === workspaceId);
35
+ const workspaces = await client.workspaces();
36
+ const workspace = account.workspace.startsWith("wsp_") ? workspaces.find((candidate) => candidate.id === account.workspace) : findClickClackWorkspace(workspaces, account.workspace);
37
37
  if (!workspace) throw new Error(`ClickClack workspace not found: ${account.workspace}`);
38
38
  return {
39
39
  status: "connected",
@@ -83,20 +83,12 @@ function formatClickClackConnectionLog(result) {
83
83
  }
84
84
  }
85
85
  async function verifyClickClackAccountAfterSetup(params) {
86
- try {
87
- const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
88
- cfg: params.cfg,
89
- accountId: params.accountId
90
- }));
91
- if (message) params.runtime.log(message);
92
- } catch (error) {
93
- params.runtime.log(`Connection check failed: ${formatErrorMessage(error)}. Setup was saved; fix the connection and rerun setup.`);
94
- }
95
- try {
96
- params.runtime.log(await probeClickClackGatewayStatus());
97
- } catch {
98
- params.runtime.log(GATEWAY_UNKNOWN_MESSAGE);
99
- }
86
+ const message = formatClickClackConnectionLog(await checkClickClackSetupConnection({
87
+ cfg: params.cfg,
88
+ accountId: params.accountId
89
+ }));
90
+ if (message) params.runtime.log(message);
91
+ params.runtime.log(await probeClickClackGatewayStatus());
100
92
  }
101
93
  //#endregion
102
94
  export { checkClickClackSetupConnection, verifyClickClackAccountAfterSetup };
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-BJ5D55LV.mjs";
2
- import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-BFuhVeiX.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-BJ3jioqA.mjs";
2
+ import { a as setClickClackRuntime, i as getClickClackRuntime, n as buildClickClackTarget, r as parseClickClackTarget, t as clickClackPlugin } from "./.setup/channel-DRcZ_twH.mjs";
3
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 @@
1
- import { t as clickClackPlugin } from "./.setup/channel-BFuhVeiX.mjs";
2
- export { clickClackPlugin };
1
+ export { t as clickClackPlugin } from "./.setup/channel-DRcZ_twH.mjs";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-hAV5Muz-.mjs";
1
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-BrlvVAQJ.mjs";
2
2
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
3
3
  //#region extensions/clickclack/index.ts
4
4
  var clickclack_default = defineBundledChannelEntry({
@@ -1,5 +1,5 @@
1
- import { h as resolveClickClackAccount } from "./.setup/setup-surface-BJ5D55LV.mjs";
2
- import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-BFuhVeiX.mjs";
1
+ import { h as resolveClickClackAccount } from "./.setup/setup-surface-BJ3jioqA.mjs";
2
+ import { a as setClickClackRuntime, r as parseClickClackTarget } from "./.setup/channel-DRcZ_twH.mjs";
3
3
  import { a as createClickClackClient } from "./.setup/resolve-Br3ylpSz.mjs";
4
- import { t as registerClickClackDiscussions } from "./.setup/runtime-api-hAV5Muz-.mjs";
4
+ import { t as registerClickClackDiscussions } from "./.setup/runtime-api-BrlvVAQJ.mjs";
5
5
  export { createClickClackClient, parseClickClackTarget, registerClickClackDiscussions, resolveClickClackAccount, setClickClackRuntime };
@@ -1,2 +1 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./.setup/secret-contract-CF7TKtkU.mjs";
2
- export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
1
+ export { t as channelSecrets, n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./.setup/secret-contract-CF7TKtkU.mjs";
@@ -4,11 +4,11 @@ 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-DpKXmzV1.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/setup-plugin-api-0RMVREuM.mjs", import.meta.url).href),
8
8
  exportName: "clickClackSetupPlugin"
9
9
  },
10
10
  secrets: {
11
- specifier: fileURLToPath(new URL(".setup/secret-contract-api-CBcsgIYZ.mjs", import.meta.url).href),
11
+ specifier: fileURLToPath(new URL(".setup/secret-contract-api--45pFCg3.mjs", import.meta.url).href),
12
12
  exportName: "channelSecrets"
13
13
  }
14
14
  });
@@ -1,2 +1 @@
1
- import { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-Qi8u_lra.mjs";
2
- export { clickClackSetupPlugin };
1
+ export { t as clickClackSetupPlugin } from "./.setup/setup-plugin-api-Ccxm2ydn.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/clickclack",
3
- "version": "2026.10.5-beta.4",
3
+ "version": "2026.10.10-beta.2",
4
4
  "description": "OpenClaw ClickClack channel plugin",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "zod": "4.6.5"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.10.5-beta.4"
16
+ "openclaw": ">=2026.10.10-beta.2"
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.5-beta.4"
142
+ "pluginApi": ">=2026.10.10-beta.2"
143
143
  },
144
144
  "build": {
145
- "openclawVersion": "2026.10.5-beta.4",
145
+ "openclawVersion": "2026.10.10-beta.2",
146
146
  "bundledDist": false
147
147
  },
148
148
  "release": {
@@ -1,3 +0,0 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-CF7TKtkU.mjs";
2
- import "../secret-contract-api.js";
3
- export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -1,2 +0,0 @@
1
- import { t as clickClackSetupPlugin } from "./setup-plugin-api-Qi8u_lra.mjs";
2
- export { clickClackSetupPlugin };