@openclaw/clickclack 2026.7.2-beta.2 → 2026.7.2-beta.4

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,4 +1,4 @@
1
- import { a as createClickClackClient, c as CLICKCLACK_CHANNEL_ID, d as DEFAULT_ACCOUNT_ID, i as resolveWorkspaceId, l as clickClackConfigAdapter, m as resolveClickClackAccount, n as clickClackSetupAdapter, o as normalizeClickClackCorrelationId, r as resolveChannelId, s as clickClackConfigSchema, t as clickClackSetupWizard, u as clickClackMeta } from "./setup-surface-B346XaAu.js";
1
+ import { _ as clickClackMeta, b as listEnabledClickClackAccounts, c as resolveChannelId, d as createClickClackClient, g as clickClackConfigAdapter, h as CLICKCLACK_CHANNEL_ID, l as resolveWorkspaceId, m as clickClackConfigSchema, n as clickClackSetupAdapter, p as normalizeClickClackCorrelationId, r as clickClackSetupContract, t as clickClackSetupWizard, v as DEFAULT_ACCOUNT_ID, x as resolveClickClackAccount } from "./setup-surface-Baf26N-D.js";
2
2
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-BKkzzjBA.js";
3
3
  import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
4
4
  import { buildChannelProgressDraftLine, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, deriveDurableFinalDeliveryRequirements } from "openclaw/plugin-sdk/channel-outbound";
@@ -8,10 +8,11 @@ import { sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
8
8
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
9
9
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
10
10
  import { listNativeCommandSpecsForConfig } from "openclaw/plugin-sdk/native-command-registry";
11
+ import { createChannelInboundEnvelopeBuilder } from "openclaw/plugin-sdk/channel-inbound";
11
12
  import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
12
- import { createHash } from "node:crypto";
13
+ import { createHash, randomUUID } from "node:crypto";
13
14
  import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
14
- import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
15
+ import { renderMarkdownWithMarkers, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
15
16
  //#region extensions/clickclack/src/runtime.ts
16
17
  /**
17
18
  * Runtime store for host-provided OpenClaw services used by the ClickClack
@@ -327,6 +328,336 @@ function createClickClackActivityPublisher(params) {
327
328
  };
328
329
  }
329
330
  //#endregion
331
+ //#region extensions/clickclack/src/discussions/binding-generation.ts
332
+ const DISCUSSION_GENERATIONS_NAMESPACE = "discussion-binding-generations";
333
+ const MAX_PENDING_DISCUSSION_GENERATIONS = 1e4;
334
+ const storesByRuntime$2 = /* @__PURE__ */ new WeakMap();
335
+ function getGenerationStore(runtime) {
336
+ const existing = storesByRuntime$2.get(runtime);
337
+ if (existing) return existing;
338
+ const created = runtime.state.openSyncKeyedStore({
339
+ namespace: DISCUSSION_GENERATIONS_NAMESPACE,
340
+ maxEntries: MAX_PENDING_DISCUSSION_GENERATIONS,
341
+ overflowPolicy: "reject-new"
342
+ });
343
+ storesByRuntime$2.set(runtime, created);
344
+ return created;
345
+ }
346
+ /** Reserves a generation so an interrupted channel create can be adopted on retry. */
347
+ function reserveDiscussionBindingGeneration(params) {
348
+ const store = getGenerationStore(params.runtime);
349
+ const existing = store.lookup(params.sessionKey);
350
+ if (existing?.destinationIdentity === params.destinationIdentity) return existing.generation;
351
+ const generation = (params.createGeneration ?? randomUUID)();
352
+ store.register(params.sessionKey, {
353
+ destinationIdentity: params.destinationIdentity,
354
+ generation
355
+ });
356
+ return generation;
357
+ }
358
+ /** Clears only the completed reservation; future opens must mint a new ownership ref. */
359
+ function clearDiscussionBindingGeneration(params) {
360
+ const store = getGenerationStore(params.runtime);
361
+ const existing = store.lookup(params.sessionKey);
362
+ if (!existing) return;
363
+ if (params.expectedGeneration && existing.generation !== params.expectedGeneration) return;
364
+ store.delete(params.sessionKey);
365
+ }
366
+ /** Quarantines a destination before the first fallible channel create. */
367
+ function recordPendingDiscussionOpen(params) {
368
+ const store = getGenerationStore(params.runtime);
369
+ const existing = store.lookup(params.sessionKey);
370
+ if (!existing || existing.generation !== params.generation) throw new Error("ClickClack discussion generation changed before channel creation");
371
+ store.register(params.sessionKey, {
372
+ ...existing,
373
+ pending: params.pending
374
+ });
375
+ }
376
+ function listPendingDiscussionOpens(runtime) {
377
+ return getGenerationStore(runtime).entries().flatMap((entry) => entry.value.pending ? [{
378
+ sessionKey: entry.key,
379
+ generation: entry.value.generation,
380
+ ...entry.value.pending
381
+ }] : []);
382
+ }
383
+ function hasPendingDiscussionOpenForDestination(params) {
384
+ const serverBaseUrl = params.serverBaseUrl.replace(/\/+$/u, "");
385
+ return listPendingDiscussionOpens(params.runtime).some((pending) => pending.serverBaseUrl === serverBaseUrl && pending.workspaceId === params.workspaceId);
386
+ }
387
+ //#endregion
388
+ //#region extensions/clickclack/src/discussions/naming.ts
389
+ function shortSessionHash(sessionKey) {
390
+ return createHash("sha256").update(sessionKey).digest("hex").slice(0, 32);
391
+ }
392
+ function fallbackDiscussionLabel(sessionKey) {
393
+ return `s-${shortSessionHash(sessionKey)}`;
394
+ }
395
+ function resolveDiscussionLabel(label, sessionKey) {
396
+ return label?.trim() || fallbackDiscussionLabel(sessionKey);
397
+ }
398
+ function slugifyDiscussionLabel(label, sessionKey) {
399
+ return label.normalize("NFKD").replace(/[\u0300-\u036f]/gu, "").toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80).replace(/-+$/gu, "") || fallbackDiscussionLabel(sessionKey);
400
+ }
401
+ function discussionSessionPeerId(identity) {
402
+ return `disc-${createHash("sha256").update([
403
+ identity.mainSessionKey,
404
+ identity.sessionId,
405
+ identity.serverBaseUrl,
406
+ identity.channelId,
407
+ identity.externalRef
408
+ ].join("\0")).digest("hex").slice(0, 32)}`;
409
+ }
410
+ function isDiscussionSessionKey(sessionKey) {
411
+ return sessionKey.includes(":clickclack:") && /:channel:disc-[0-9a-f]{32}$/u.test(sessionKey);
412
+ }
413
+ function discussionExternalRef(installationId, mainSessionKey, sessionId, destinationIdentity, bindingGeneration) {
414
+ return `openclaw:${installationId}:${shortSessionHash([
415
+ mainSessionKey,
416
+ sessionId,
417
+ destinationIdentity,
418
+ bindingGeneration
419
+ ].join("\0"))}`;
420
+ }
421
+ function discussionCredentialFingerprint(token) {
422
+ return createHash("sha256").update(token).digest("hex");
423
+ }
424
+ function discussionSessionKey(params) {
425
+ return params.runtime.channel.routing.buildAgentSessionKey({
426
+ agentId: params.agentId,
427
+ channel: "clickclack",
428
+ accountId: params.accountId,
429
+ peer: {
430
+ kind: "channel",
431
+ id: discussionSessionPeerId(params)
432
+ }
433
+ });
434
+ }
435
+ //#endregion
436
+ //#region extensions/clickclack/src/discussions/binding-store.ts
437
+ function bindingMatchesSessionIncarnation(runtime, sessionKey, binding) {
438
+ const entry = runtime.agent.session.getSessionEntry({
439
+ sessionKey,
440
+ readConsistency: "latest"
441
+ });
442
+ return Boolean(entry && binding.sessionId && entry.sessionId === binding.sessionId);
443
+ }
444
+ function bindingMatchesActiveSessionIncarnation(runtime, sessionKey, binding) {
445
+ const entry = runtime.agent.session.getSessionEntry({
446
+ sessionKey,
447
+ readConsistency: "latest"
448
+ });
449
+ return Boolean(entry && binding.sessionId && entry.sessionId === binding.sessionId && entry.archivedAt === void 0);
450
+ }
451
+ const DISCUSSION_BINDINGS_NAMESPACE = "discussion-bindings";
452
+ const MAX_DISCUSSION_BINDINGS = 1e4;
453
+ const storesByRuntime$1 = /* @__PURE__ */ new WeakMap();
454
+ function channelKey(serverBaseUrl, channelId) {
455
+ return `${serverBaseUrl.replace(/\/+$/u, "")}\0${channelId}`;
456
+ }
457
+ /** SQLite-backed session/channel bindings with a process-local inbound lookup index. */
458
+ var ClickClackDiscussionBindingStore = class {
459
+ #store;
460
+ #sessionByChannel = /* @__PURE__ */ new Map();
461
+ #mainByDiscussionSession = /* @__PURE__ */ new Map();
462
+ #runtime;
463
+ constructor(runtime) {
464
+ this.#runtime = runtime;
465
+ this.#store = runtime.state.openSyncKeyedStore({
466
+ namespace: DISCUSSION_BINDINGS_NAMESPACE,
467
+ maxEntries: MAX_DISCUSSION_BINDINGS,
468
+ overflowPolicy: "reject-new"
469
+ });
470
+ for (const entry of this.#store.entries()) this.#index(entry.key, entry.value);
471
+ }
472
+ get(sessionKey) {
473
+ return this.#store.lookup(sessionKey);
474
+ }
475
+ hasCapacity(sessionKey) {
476
+ return this.get(sessionKey) !== void 0 || this.#store.entries().length < MAX_DISCUSSION_BINDINGS;
477
+ }
478
+ getByChannel(serverBaseUrl, channelId) {
479
+ const key = channelKey(serverBaseUrl, channelId);
480
+ const sessionKey = this.#sessionByChannel.get(key);
481
+ if (!sessionKey) return;
482
+ const binding = this.get(sessionKey);
483
+ if (!binding) {
484
+ this.#sessionByChannel.delete(key);
485
+ return;
486
+ }
487
+ return {
488
+ sessionKey,
489
+ binding
490
+ };
491
+ }
492
+ set(sessionKey, binding) {
493
+ const previous = this.get(sessionKey);
494
+ this.#store.register(sessionKey, binding);
495
+ if (previous) this.#unindex(sessionKey, previous);
496
+ this.#index(sessionKey, binding);
497
+ }
498
+ delete(sessionKey) {
499
+ const previous = this.get(sessionKey);
500
+ const deleted = this.#store.delete(sessionKey);
501
+ if (deleted && previous) this.#unindex(sessionKey, previous);
502
+ return deleted;
503
+ }
504
+ getByDiscussionSession(sideSessionKey) {
505
+ const sessionKey = this.#mainByDiscussionSession.get(sideSessionKey);
506
+ if (!sessionKey) return;
507
+ const binding = this.get(sessionKey);
508
+ return binding ? {
509
+ sessionKey,
510
+ binding
511
+ } : void 0;
512
+ }
513
+ entries() {
514
+ return this.#store.entries().map((entry) => ({
515
+ sessionKey: entry.key,
516
+ binding: entry.value
517
+ }));
518
+ }
519
+ #index(sessionKey, binding) {
520
+ this.#sessionByChannel.set(channelKey(binding.serverBaseUrl, binding.channelId), sessionKey);
521
+ const sideSessionKey = discussionSessionKey({
522
+ runtime: this.#runtime,
523
+ agentId: binding.agentId,
524
+ mainSessionKey: sessionKey,
525
+ sessionId: binding.sessionId,
526
+ accountId: binding.accountId,
527
+ serverBaseUrl: binding.serverBaseUrl,
528
+ channelId: binding.channelId,
529
+ externalRef: binding.externalRef
530
+ });
531
+ if (sideSessionKey) this.#mainByDiscussionSession.set(sideSessionKey, sessionKey);
532
+ }
533
+ #unindex(sessionKey, binding) {
534
+ this.#sessionByChannel.delete(channelKey(binding.serverBaseUrl, binding.channelId));
535
+ const sideSessionKey = discussionSessionKey({
536
+ runtime: this.#runtime,
537
+ agentId: binding.agentId,
538
+ mainSessionKey: sessionKey,
539
+ sessionId: binding.sessionId,
540
+ accountId: binding.accountId,
541
+ serverBaseUrl: binding.serverBaseUrl,
542
+ channelId: binding.channelId,
543
+ externalRef: binding.externalRef
544
+ });
545
+ if (sideSessionKey) this.#mainByDiscussionSession.delete(sideSessionKey);
546
+ }
547
+ };
548
+ function getClickClackDiscussionBindingStore(runtime) {
549
+ const existing = storesByRuntime$1.get(runtime);
550
+ if (existing) return existing;
551
+ const created = new ClickClackDiscussionBindingStore(runtime);
552
+ storesByRuntime$1.set(runtime, created);
553
+ return created;
554
+ }
555
+ //#endregion
556
+ //#region extensions/clickclack/src/discussions/eligibility.ts
557
+ function discussionAccounts(cfg) {
558
+ return listEnabledClickClackAccounts(cfg).filter((account) => account.configured && account.discussions.enabled);
559
+ }
560
+ function normalizedServerBaseUrl(account) {
561
+ return account.baseUrl.replace(/\/+$/u, "");
562
+ }
563
+ /** Resolves the sole live account and rejects bindings pinned to an older destination. */
564
+ function resolveDiscussionBindingAccount(cfg, binding) {
565
+ const accounts = discussionAccounts(cfg);
566
+ if (accounts.length !== 1) return { state: "unavailable" };
567
+ const account = accounts[0];
568
+ if (!account) return { state: "unavailable" };
569
+ if (account.accountId !== binding.accountId || normalizedServerBaseUrl(account) !== binding.serverBaseUrl || account.discussions.workspace !== binding.workspaceRef || binding.credentialFingerprint !== void 0 && discussionCredentialFingerprint(account.token) !== binding.credentialFingerprint) return {
570
+ state: "stale",
571
+ account
572
+ };
573
+ return {
574
+ state: "active",
575
+ account
576
+ };
577
+ }
578
+ //#endregion
579
+ //#region extensions/clickclack/src/discussions/revoked-channel-store.ts
580
+ const REVOKED_CHANNELS_NAMESPACE = "discussion-revoked-channels";
581
+ const MAX_REVOKED_CHANNELS = 1e5;
582
+ const storesByRuntime = /* @__PURE__ */ new WeakMap();
583
+ function revokedChannelKey(params) {
584
+ return [params.serverBaseUrl.replace(/\/+$/u, ""), params.channelId].join("\0");
585
+ }
586
+ function getStore(runtime) {
587
+ const existing = storesByRuntime.get(runtime);
588
+ if (existing) return existing;
589
+ const created = runtime.state.openSyncKeyedStore({
590
+ namespace: REVOKED_CHANNELS_NAMESPACE,
591
+ maxEntries: MAX_REVOKED_CHANNELS,
592
+ overflowPolicy: "reject-new"
593
+ });
594
+ storesByRuntime.set(runtime, created);
595
+ return created;
596
+ }
597
+ /** Records managed ownership before its live binding is released. */
598
+ function markClickClackDiscussionChannelRevoked(runtime, binding) {
599
+ const value = {
600
+ accountId: binding.accountId,
601
+ serverBaseUrl: binding.serverBaseUrl,
602
+ channelId: binding.channelId,
603
+ revokedAt: Date.now()
604
+ };
605
+ getStore(runtime).register(revokedChannelKey(value), value);
606
+ }
607
+ function markClickClackDiscussionChannelIdentityRevoked(params) {
608
+ const value = {
609
+ accountId: params.accountId,
610
+ serverBaseUrl: params.serverBaseUrl.replace(/\/+$/u, ""),
611
+ channelId: params.channelId,
612
+ revokedAt: Date.now()
613
+ };
614
+ getStore(params.runtime).register(revokedChannelKey(value), value);
615
+ }
616
+ function clearClickClackDiscussionChannelRevoked(params) {
617
+ getStore(params.runtime).delete(revokedChannelKey(params));
618
+ }
619
+ /** Distinguishes a released managed channel from a genuinely ordinary channel. */
620
+ function isClickClackDiscussionChannelRevoked(params) {
621
+ return Boolean(getStore(params.runtime).lookup(revokedChannelKey(params)));
622
+ }
623
+ //#endregion
624
+ //#region extensions/clickclack/src/discussions/routing.ts
625
+ function resolveClickClackDiscussionRoute(params) {
626
+ if (isClickClackDiscussionChannelRevoked(params)) return { state: "revoked" };
627
+ const matched = getClickClackDiscussionBindingStore(params.runtime).getByChannel(params.serverBaseUrl, params.channelId);
628
+ if (!matched) return { state: hasPendingDiscussionOpenForDestination(params) ? "revoked" : "unbound" };
629
+ if (matched.binding.accountId !== params.accountId) return { state: "revoked" };
630
+ if (matched.binding.serverBaseUrl !== params.serverBaseUrl.replace(/\/+$/u, "")) return { state: "revoked" };
631
+ if (matched.binding.archived) return { state: "revoked" };
632
+ if (resolveDiscussionBindingAccount(params.config, matched.binding).state !== "active") return { state: "revoked" };
633
+ if (!bindingMatchesActiveSessionIncarnation(params.runtime, matched.sessionKey, matched.binding)) return { state: "revoked" };
634
+ const sessionKey = discussionSessionKey({
635
+ runtime: params.runtime,
636
+ agentId: matched.binding.agentId,
637
+ mainSessionKey: matched.sessionKey,
638
+ sessionId: matched.binding.sessionId,
639
+ accountId: params.accountId,
640
+ serverBaseUrl: matched.binding.serverBaseUrl,
641
+ channelId: matched.binding.channelId,
642
+ externalRef: matched.binding.externalRef
643
+ });
644
+ if (!sessionKey) return { state: "revoked" };
645
+ return {
646
+ state: "active",
647
+ route: {
648
+ agentId: matched.binding.agentId,
649
+ sessionKey,
650
+ systemPrompt: [
651
+ "You are the side agent for a ClickClack discussion attached to an OpenClaw session.",
652
+ `The main session key is ${matched.sessionKey}.`,
653
+ "Observe it with sessions_history and session_status (using changesSince for incremental checks).",
654
+ "Use sessions_send to relay or steer the main session only when the humans in this discussion ask you to.",
655
+ "These session tools are host-scoped to the attached main session; do not attempt session discovery or alternate targets."
656
+ ].join(" ")
657
+ }
658
+ };
659
+ }
660
+ //#endregion
330
661
  //#region extensions/clickclack/src/target.ts
331
662
  /**
332
663
  * Parses `channel:name`, `thread:msg_id`, `dm:usr_id`, or a bare channel name.
@@ -377,6 +708,42 @@ function looksLikeClickClackTarget(raw) {
377
708
  * and direct messages.
378
709
  */
379
710
  const CLICKCLACK_MAX_UPLOAD_BYTES = 64 * 1024 * 1024;
711
+ const CLICKCLACK_FORMAT_PROFILE = {
712
+ mechanism: "markdown",
713
+ constructs: {
714
+ bold: "native",
715
+ italic: "native",
716
+ underline: "native",
717
+ strikethrough: "native",
718
+ spoiler: "native",
719
+ codeInline: "native",
720
+ codeBlock: "native",
721
+ codeLanguage: "native",
722
+ linkLabel: "native",
723
+ heading: "native",
724
+ bulletList: "native",
725
+ orderedList: "native",
726
+ taskList: "native",
727
+ table: "native",
728
+ blockquote: "native",
729
+ image: "native",
730
+ mention: "native"
731
+ },
732
+ chunk: {
733
+ limit: 1024 * 1024,
734
+ unit: "bytes"
735
+ }
736
+ };
737
+ function renderClickClackMarkdown(markdown) {
738
+ return renderMarkdownWithMarkers({
739
+ text: markdown,
740
+ styles: [],
741
+ links: []
742
+ }, {
743
+ styleMarkers: {},
744
+ escapeText: (text) => text
745
+ }, CLICKCLACK_FORMAT_PROFILE);
746
+ }
380
747
  async function createTargetMessage(params) {
381
748
  const parsed = parseClickClackTarget(params.to);
382
749
  const explicitThreadId = params.threadId == null ? "" : String(params.threadId);
@@ -452,7 +819,7 @@ function createOutboundContext(params) {
452
819
  return {
453
820
  account,
454
821
  client: createClickClackClient({
455
- baseUrl: account.baseUrl,
822
+ baseUrl: account.apiEndpoint,
456
823
  token: account.token,
457
824
  correlationId: params.correlationId
458
825
  })
@@ -463,7 +830,7 @@ function createOutboundContext(params) {
463
830
  * message id, or undefined when sanitization removes all content.
464
831
  */
465
832
  async function sendClickClackText(params) {
466
- const text = sanitizeAssistantVisibleText(params.text);
833
+ const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text));
467
834
  if (!text) return;
468
835
  const { account, client } = createOutboundContext(params);
469
836
  const workspaceId = await resolveWorkspaceId(client, account.workspace);
@@ -523,7 +890,7 @@ async function sendClickClackMedia(params) {
523
890
  ...nonces.upload ? { nonce: nonces.upload } : {}
524
891
  });
525
892
  }
526
- const text = sanitizeAssistantVisibleText(params.text) || mediaFilename || upload.filename || "attachment";
893
+ const text = renderClickClackMarkdown(sanitizeAssistantVisibleText(params.text)) || mediaFilename || upload.filename || "attachment";
527
894
  const message = await createTargetMessage({
528
895
  client,
529
896
  workspaceId,
@@ -740,6 +1107,8 @@ async function handleClickClackInbound(params) {
740
1107
  message
741
1108
  });
742
1109
  if (!access.shouldDispatch) return;
1110
+ const conversationId = message.channel_id || message.direct_conversation_id;
1111
+ if (!conversationId) return;
743
1112
  const isDirect = Boolean(message.direct_conversation_id);
744
1113
  const target = buildClickClackTarget(isDirect ? {
745
1114
  chatType: "direct",
@@ -750,13 +1119,29 @@ async function handleClickClackInbound(params) {
750
1119
  kind: "channel",
751
1120
  id: message.channel_id ?? ""
752
1121
  });
753
- const route = resolveAccountAgentRoute({
1122
+ const accountRoute = resolveAccountAgentRoute({
754
1123
  cfg: params.config,
755
1124
  account: params.account,
756
1125
  target,
757
1126
  isDirect
758
1127
  });
759
- if (params.account.replyMode === "model") {
1128
+ const discussionResolution = !isDirect && message.channel_id ? resolveClickClackDiscussionRoute({
1129
+ runtime,
1130
+ config: params.config,
1131
+ accountId: params.account.accountId,
1132
+ serverBaseUrl: params.account.baseUrl,
1133
+ workspaceId: message.workspace_id,
1134
+ channelId: message.channel_id
1135
+ }) : { state: "unbound" };
1136
+ if (discussionResolution.state === "revoked") return;
1137
+ const discussionRoute = discussionResolution.state === "active" ? discussionResolution.route : void 0;
1138
+ const route = discussionRoute ? {
1139
+ ...accountRoute,
1140
+ agentId: discussionRoute.agentId,
1141
+ sessionKey: discussionRoute.sessionKey,
1142
+ lastRoutePolicy: "session"
1143
+ } : accountRoute;
1144
+ if (params.account.replyMode === "model" && !discussionRoute) {
760
1145
  await dispatchModelReply({
761
1146
  account: params.account,
762
1147
  cfg: params.config,
@@ -771,7 +1156,7 @@ async function handleClickClackInbound(params) {
771
1156
  let activity;
772
1157
  if (params.account.agentActivity && (message.channel_id || message.direct_conversation_id)) activity = createClickClackActivityPublisher({
773
1158
  client: createClickClackClient({
774
- baseUrl: params.account.baseUrl,
1159
+ baseUrl: params.account.apiEndpoint,
775
1160
  token: params.account.token,
776
1161
  correlationId: params.correlationId
777
1162
  }),
@@ -785,46 +1170,63 @@ async function handleClickClackInbound(params) {
785
1170
  }
786
1171
  });
787
1172
  const senderName = message.author?.display_name || message.author_id;
788
- const previousTimestamp = runtime.channel.session.readSessionUpdatedAt({
789
- storePath: runtime.channel.session.resolveStorePath(params.config.session?.store, { agentId: route.agentId }),
790
- sessionKey: route.sessionKey
791
- });
792
- const body = runtime.channel.reply.formatAgentEnvelope({
1173
+ const body = createChannelInboundEnvelopeBuilder({
1174
+ cfg: params.config,
1175
+ route
1176
+ })({
793
1177
  channel: "ClickClack",
794
1178
  from: senderName,
795
1179
  timestamp: new Date(message.created_at),
796
- previousTimestamp,
797
- envelope: runtime.channel.reply.resolveEnvelopeFormatOptions(params.config),
798
1180
  body: message.body
799
1181
  });
800
- const storePath = runtime.channel.session.resolveStorePath(params.config.session?.store, { agentId: route.agentId });
801
- const ctxPayload = runtime.channel.reply.finalizeInboundContext({
802
- Body: body,
803
- BodyForAgent: message.body,
804
- RawBody: message.body,
805
- CommandBody: message.body,
806
- From: target,
807
- To: target,
808
- SessionKey: route.sessionKey,
809
- AccountId: route.accountId ?? params.account.accountId,
810
- ChatType: isDirect ? "direct" : "group",
811
- WasMentioned: isDirect ? void 0 : true,
812
- ConversationLabel: isDirect ? senderName : message.channel_id,
813
- GroupChannel: message.channel_id,
814
- NativeChannelId: message.channel_id || message.direct_conversation_id,
815
- MessageThreadId: message.parent_message_id ? message.thread_root_id : void 0,
816
- ThreadParentId: message.parent_message_id ? message.thread_root_id : void 0,
817
- SenderName: senderName,
818
- SenderId: message.author_id,
819
- Provider: CHANNEL_ID$1,
820
- Surface: CHANNEL_ID$1,
821
- MessageSid: message.id,
822
- MessageSidFull: message.id,
823
- ReplyToId: message.id,
824
- Timestamp: message.created_at,
825
- OriginatingChannel: CHANNEL_ID$1,
826
- OriginatingTo: target,
827
- CommandAuthorized: access.commandAuthorized
1182
+ const ctxPayload = runtime.channel.inbound.buildContext({
1183
+ channel: CHANNEL_ID$1,
1184
+ accountId: route.accountId ?? params.account.accountId,
1185
+ messageId: message.id,
1186
+ messageIdFull: message.id,
1187
+ timestamp: new Date(message.created_at).getTime(),
1188
+ from: target,
1189
+ sender: {
1190
+ id: message.author_id,
1191
+ name: senderName
1192
+ },
1193
+ conversation: {
1194
+ kind: isDirect ? "direct" : "group",
1195
+ id: conversationId,
1196
+ label: isDirect ? senderName : message.channel_id,
1197
+ threadId: message.parent_message_id ? message.thread_root_id : void 0,
1198
+ nativeChannelId: conversationId
1199
+ },
1200
+ route: {
1201
+ agentId: route.agentId,
1202
+ dmScope: route.dmScope,
1203
+ accountId: route.accountId,
1204
+ routeSessionKey: route.sessionKey
1205
+ },
1206
+ reply: {
1207
+ to: target,
1208
+ originatingTo: target,
1209
+ replyToId: message.id,
1210
+ messageThreadId: message.parent_message_id ? message.thread_root_id : void 0,
1211
+ threadParentId: message.parent_message_id ? message.thread_root_id : void 0
1212
+ },
1213
+ message: {
1214
+ body,
1215
+ bodyForAgent: message.body,
1216
+ rawBody: message.body,
1217
+ commandBody: message.body
1218
+ },
1219
+ access: {
1220
+ commands: { authorized: access.commandAuthorized },
1221
+ mentions: {
1222
+ canDetectMention: !isDirect,
1223
+ wasMentioned: !isDirect
1224
+ }
1225
+ },
1226
+ extra: {
1227
+ GroupChannel: message.channel_id,
1228
+ ...discussionRoute ? { GroupSystemPrompt: discussionRoute.systemPrompt } : {}
1229
+ }
828
1230
  });
829
1231
  const runId = resolveClickClackAgentRunId(message.id);
830
1232
  const activityReplyOptions = activity ? {
@@ -840,16 +1242,16 @@ async function handleClickClackInbound(params) {
840
1242
  suppressDefaultToolProgressMessages: true,
841
1243
  allowProgressCallbacksWhenSourceDeliverySuppressed: true
842
1244
  } : void 0;
843
- const dispatchPromise = runtime.channel.inbound.dispatchReply({
1245
+ const dispatchPromise = runtime.channel.inbound.dispatch({
844
1246
  cfg: params.config,
845
1247
  channel: CHANNEL_ID$1,
846
1248
  accountId: params.account.accountId,
847
- agentId: route.agentId,
848
- routeSessionKey: route.sessionKey,
849
- storePath,
1249
+ route: {
1250
+ agentId: route.agentId,
1251
+ dmScope: route.dmScope,
1252
+ sessionKey: route.sessionKey
1253
+ },
850
1254
  ctxPayload,
851
- recordInboundSession: runtime.channel.session.recordInboundSession,
852
- dispatchReplyWithBufferedBlockDispatcher: runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
853
1255
  toolsAllow: params.account.toolsAllow,
854
1256
  replyOptions: runId || activityReplyOptions ? {
855
1257
  ...runId ? { runId } : {},
@@ -943,7 +1345,7 @@ async function processEvent(params) {
943
1345
  const correlationId = eventCorrelationId(params.event);
944
1346
  const message = await resolveEventMessage({
945
1347
  client: correlationId ? createClickClackClient({
946
- baseUrl: params.account.baseUrl,
1348
+ baseUrl: params.account.apiEndpoint,
947
1349
  token: params.account.token,
948
1350
  correlationId
949
1351
  }) : params.client,
@@ -989,7 +1391,7 @@ async function startClickClackGatewayAccount(ctx) {
989
1391
  });
990
1392
  if (!configuredAccount.configured) throw new Error(`ClickClack is not configured for account "${configuredAccount.accountId}"`);
991
1393
  const client = createClickClackClient({
992
- baseUrl: configuredAccount.baseUrl,
1394
+ baseUrl: configuredAccount.apiEndpoint,
993
1395
  token: configuredAccount.token
994
1396
  });
995
1397
  const workspaceId = await resolveWorkspaceId(client, configuredAccount.workspace);
@@ -1211,6 +1613,7 @@ const clickClackPlugin = createChatChannelPlugin({
1211
1613
  configSchema: clickClackConfigSchema,
1212
1614
  config: clickClackConfigAdapter,
1213
1615
  setup: clickClackSetupAdapter,
1616
+ setupContract: clickClackSetupContract,
1214
1617
  setupWizard: clickClackSetupWizard,
1215
1618
  secrets: {
1216
1619
  secretTargetRegistryEntries,
@@ -1316,4 +1719,4 @@ const clickClackPlugin = createChatChannelPlugin({
1316
1719
  }
1317
1720
  });
1318
1721
  //#endregion
1319
- export { setClickClackRuntime as a, getClickClackRuntime as i, buildClickClackTarget as n, parseClickClackTarget as r, clickClackPlugin as t };
1722
+ export { reserveDiscussionBindingGeneration as C, recordPendingDiscussionOpen as S, setClickClackRuntime as T, isDiscussionSessionKey as _, isClickClackDiscussionChannelRevoked as a, clearDiscussionBindingGeneration as b, discussionAccounts as c, bindingMatchesActiveSessionIncarnation as d, bindingMatchesSessionIncarnation as f, fallbackDiscussionLabel as g, discussionExternalRef as h, clearClickClackDiscussionChannelRevoked as i, normalizedServerBaseUrl as l, discussionCredentialFingerprint as m, buildClickClackTarget as n, markClickClackDiscussionChannelIdentityRevoked as o, getClickClackDiscussionBindingStore as p, parseClickClackTarget as r, markClickClackDiscussionChannelRevoked as s, clickClackPlugin as t, resolveDiscussionBindingAccount as u, resolveDiscussionLabel as v, getClickClackRuntime as w, listPendingDiscussionOpens as x, slugifyDiscussionLabel as y };
@@ -1,2 +1,2 @@
1
- import { t as clickClackPlugin } from "./channel-BnREQIP8.js";
1
+ import { t as clickClackPlugin } from "./channel-BDq0KWDB.js";
2
2
  export { clickClackPlugin };
@@ -0,0 +1,48 @@
1
+ import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor";
2
+ //#region extensions/clickclack/src/doctor-contract.ts
3
+ function stripTimeoutSeconds(value) {
4
+ const record = asObjectRecord(value);
5
+ if (!record) return {
6
+ value,
7
+ changed: false
8
+ };
9
+ let changed = false;
10
+ const next = {};
11
+ for (const [key, child] of Object.entries(record)) {
12
+ if (key === "timeoutSeconds") {
13
+ changed = true;
14
+ continue;
15
+ }
16
+ const stripped = stripTimeoutSeconds(child);
17
+ changed = changed || stripped.changed;
18
+ next[key] = stripped.value;
19
+ }
20
+ return {
21
+ value: changed ? next : value,
22
+ changed
23
+ };
24
+ }
25
+ function normalizeCompatibilityConfig({ cfg }) {
26
+ const rawEntry = asObjectRecord(cfg.channels?.clickclack);
27
+ if (!rawEntry) return {
28
+ config: cfg,
29
+ changes: []
30
+ };
31
+ const stripped = stripTimeoutSeconds(rawEntry);
32
+ if (!stripped.changed) return {
33
+ config: cfg,
34
+ changes: []
35
+ };
36
+ return {
37
+ config: {
38
+ ...cfg,
39
+ channels: {
40
+ ...cfg.channels,
41
+ clickclack: stripped.value
42
+ }
43
+ },
44
+ changes: ["Removed retired ClickClack timeout tuning knobs."]
45
+ };
46
+ }
47
+ //#endregion
48
+ export { normalizeCompatibilityConfig };