@openclaw/clickclack 2026.7.1-beta.1 → 2026.7.1-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.
package/dist/api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as getClickClackRuntime, c as DEFAULT_ACCOUNT_ID, d as resolveClickClackAccount, f as resolveDefaultClickClackAccountId, i as createClickClackClient, l as listClickClackAccountIds, n as buildClickClackTarget, o as setClickClackRuntime, r as parseClickClackTarget, s as clickClackConfigSchema, t as clickClackPlugin, u as listEnabledClickClackAccounts } from "./channel-OEzEDUUh.js";
1
+ import { a as getClickClackRuntime, c as DEFAULT_ACCOUNT_ID, d as resolveClickClackAccount, f as resolveDefaultClickClackAccountId, i as createClickClackClient, l as listClickClackAccountIds, n as buildClickClackTarget, o as setClickClackRuntime, r as parseClickClackTarget, s as clickClackConfigSchema, t as clickClackPlugin, u as listEnabledClickClackAccounts } from "./channel-1ZnJZTPl.js";
2
2
  export { DEFAULT_ACCOUNT_ID, buildClickClackTarget, clickClackConfigSchema, clickClackPlugin, createClickClackClient, getClickClackRuntime, listClickClackAccountIds, listEnabledClickClackAccounts, parseClickClackTarget, resolveClickClackAccount, resolveDefaultClickClackAccountId, setClickClackRuntime };
@@ -6,15 +6,17 @@ import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-
6
6
  import { buildSecretInputSchema, normalizeResolvedSecretInputString, normalizeSecretInputString, resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
7
7
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import { buildChannelOutboundSessionRoute, buildThreadAwareOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
9
- import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
9
+ import { buildChannelProgressDraftLine, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { getChatChannelMeta } from "openclaw/plugin-sdk/channel-plugin-common";
11
11
  import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
12
12
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
13
13
  import { z } from "zod";
14
+ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
14
15
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
15
16
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
16
17
  import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
17
18
  import { WebSocket } from "ws";
19
+ import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
18
20
  //#region extensions/clickclack/src/accounts.ts
19
21
  /**
20
22
  * Resolves ClickClack account configuration from root channel config, named
@@ -99,6 +101,7 @@ function resolveClickClackAccount(params) {
99
101
  min: MIN_RECONNECT_MS,
100
102
  max: MAX_RECONNECT_MS
101
103
  }),
104
+ agentActivity: merged.agentActivity === true,
102
105
  config: {
103
106
  ...merged,
104
107
  allowFrom: merged.allowFrom ?? ["*"]
@@ -135,7 +138,8 @@ const ClickClackAccountConfigSchema = z.object({
135
138
  toolsAllow: z.array(z.string()).optional(),
136
139
  defaultTo: z.string().optional(),
137
140
  allowFrom: z.array(z.string()).optional(),
138
- reconnectMs: z.number().int().min(100).max(6e4).optional()
141
+ reconnectMs: z.number().int().min(100).max(6e4).optional(),
142
+ agentActivity: z.boolean().optional()
139
143
  }).strict();
140
144
  /**
141
145
  * Config schema exported to core so `openclaw doctor` and config validation
@@ -213,6 +217,18 @@ async function resolveClickClackInboundAccess(params) {
213
217
  * Thin ClickClack REST/websocket client used by gateway, resolver, and outbound
214
218
  * delivery code.
215
219
  */
220
+ /**
221
+ * Serializes optional provenance into the wire fields. Unknown JSON fields
222
+ * are ignored by servers without the provenance columns, so these are safe
223
+ * to send unconditionally when present.
224
+ */
225
+ function provenanceFields(provenance) {
226
+ const fields = {};
227
+ if (provenance?.model?.trim()) fields.author_model = provenance.model.trim();
228
+ if (provenance?.thinking?.trim()) fields.author_thinking = provenance.thinking.trim();
229
+ if (provenance?.runtime?.trim()) fields.author_runtime = provenance.runtime.trim();
230
+ return fields;
231
+ }
216
232
  const CLICKCLACK_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
217
233
  /**
218
234
  * Creates a typed client for the ClickClack API using bearer-token auth.
@@ -255,16 +271,23 @@ function createClickClackClient(options) {
255
271
  return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages?after_seq=${afterSeq}&limit=${limit}`)).messages;
256
272
  },
257
273
  thread: async (messageId) => await request(`/api/messages/${encodeURIComponent(messageId)}/thread`),
258
- createChannelMessage: async (channelId, body) => {
274
+ createChannelMessage: async (channelId, body, opts) => {
259
275
  return (await request(`/api/channels/${encodeURIComponent(channelId)}/messages`, {
260
276
  method: "POST",
261
- body: JSON.stringify({ body })
277
+ body: JSON.stringify({
278
+ body,
279
+ ...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {},
280
+ ...provenanceFields(opts?.provenance)
281
+ })
262
282
  })).message;
263
283
  },
264
- createThreadReply: async (messageId, body) => {
284
+ createThreadReply: async (messageId, body, opts) => {
265
285
  return (await request(`/api/messages/${encodeURIComponent(messageId)}/thread/replies`, {
266
286
  method: "POST",
267
- body: JSON.stringify({ body })
287
+ body: JSON.stringify({
288
+ body,
289
+ ...provenanceFields(opts?.provenance)
290
+ })
268
291
  })).message;
269
292
  },
270
293
  createDirectConversation: async (workspaceId, memberIds) => {
@@ -276,12 +299,39 @@ function createClickClackClient(options) {
276
299
  })
277
300
  })).conversation;
278
301
  },
279
- createDirectMessage: async (conversationId, body) => {
280
- return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
302
+ /**
303
+ * POSTs a durable agent activity row (agent_commentary / agent_tool)
304
+ * through the normal message create path. Requires a bot token carrying
305
+ * the agent_activity:write scope on the ClickClack side.
306
+ */
307
+ createActivityMessage: async (params) => {
308
+ if (!params.channelId && !params.conversationId) throw new Error("createActivityMessage requires a channelId or conversationId");
309
+ return (await request(params.channelId ? `/api/channels/${encodeURIComponent(params.channelId)}/messages` : `/api/dms/${encodeURIComponent(params.conversationId ?? "")}/messages`, {
281
310
  method: "POST",
311
+ body: JSON.stringify({
312
+ body: params.body,
313
+ kind: params.kind,
314
+ turn_id: params.turnId,
315
+ ...provenanceFields(params.provenance)
316
+ })
317
+ })).message;
318
+ },
319
+ /** PATCHes the body of an existing message (activity row coalescing). */
320
+ updateMessageBody: async (messageId, body) => {
321
+ return (await request(`/api/messages/${encodeURIComponent(messageId)}`, {
322
+ method: "PATCH",
282
323
  body: JSON.stringify({ body })
283
324
  })).message;
284
325
  },
326
+ createDirectMessage: async (conversationId, body, opts) => {
327
+ return (await request(`/api/dms/${encodeURIComponent(conversationId)}/messages`, {
328
+ method: "POST",
329
+ body: JSON.stringify({
330
+ body,
331
+ ...opts?.quotedMessageId ? { quoted_message_id: opts.quotedMessageId } : {}
332
+ })
333
+ })).message;
334
+ },
285
335
  events: async (workspaceId, afterCursor) => {
286
336
  const query = new URLSearchParams({ workspace_id: workspaceId });
287
337
  if (afterCursor) query.set("after_cursor", afterCursor);
@@ -297,6 +347,201 @@ function createClickClackClient(options) {
297
347
  };
298
348
  }
299
349
  //#endregion
350
+ //#region extensions/clickclack/src/activity.ts
351
+ /**
352
+ * Publishes agent activity (streamed commentary + tool progress) into
353
+ * ClickClack as durable `agent_commentary` / `agent_tool` message rows,
354
+ * coalesced so one logical step becomes one row instead of a row per frame.
355
+ *
356
+ * Ported from the clickglass agent-bridge sidecar, adapted from gateway
357
+ * websocket frames to the in-process `replyOptions.onItemEvent` seam:
358
+ *
359
+ * - Commentary/reasoning arrives as cumulative text snapshots per item id.
360
+ * Each segment becomes one durable row: POSTed when the segment starts
361
+ * streaming and PATCHed (debounced) as the snapshot grows, so prose
362
+ * interleaves chronologically with tool rows.
363
+ * - Tool/step items can emit several frames (start/update/complete) for the
364
+ * same call, sometimes with lane-prefixed ids (`tool:X`, `command:X`).
365
+ * Normalize the prefix away to one key per call, POST one row on the first
366
+ * frame, and PATCH it when a later frame carries a strictly longer body.
367
+ */
368
+ /** Debounce window for PATCHing streaming commentary snapshots. */
369
+ const CLICKCLACK_COMMENTARY_FLUSH_MS = 700;
370
+ /** Item kinds rendered as agent_tool rows; everything else is commentary. */
371
+ const TOOL_ITEM_KINDS = /* @__PURE__ */ new Set([
372
+ "tool",
373
+ "command",
374
+ "command_output",
375
+ "patch",
376
+ "search",
377
+ "api"
378
+ ]);
379
+ /** Item kinds that never become durable rows (ephemeral or plumbing lanes). */
380
+ const SKIPPED_ITEM_KINDS = /* @__PURE__ */ new Set(["lifecycle"]);
381
+ /** Item kinds emitted as cumulative commentary snapshots. */
382
+ const STREAMING_COMMENTARY_ITEM_KINDS = /* @__PURE__ */ new Set([
383
+ "preamble",
384
+ "commentary",
385
+ "analysis",
386
+ "thinking",
387
+ "reasoning"
388
+ ]);
389
+ /** Provider-specific reasoning lanes normalized into one ClickClack label. */
390
+ const THINKING_ITEM_KINDS = /* @__PURE__ */ new Set([
391
+ "analysis",
392
+ "thinking",
393
+ "reasoning"
394
+ ]);
395
+ function normalizedItemKind(payload) {
396
+ return payload.kind?.trim().toLowerCase() ?? "";
397
+ }
398
+ function commentaryBody(payload) {
399
+ const text = payload.progressText?.trim() || payload.summary?.trim() || payload.meta?.trim() || "";
400
+ if (!text) return "";
401
+ const kind = normalizedItemKind(payload);
402
+ if (THINKING_ITEM_KINDS.has(kind)) return `**Thinking**\n\n${text}`;
403
+ return text;
404
+ }
405
+ function activityBody(payload) {
406
+ const line = buildChannelProgressDraftLine({
407
+ event: "item",
408
+ itemId: payload.itemId,
409
+ toolCallId: payload.toolCallId,
410
+ itemKind: payload.kind,
411
+ title: payload.title,
412
+ name: payload.name,
413
+ phase: payload.phase,
414
+ status: payload.status,
415
+ summary: payload.summary,
416
+ progressText: payload.progressText,
417
+ meta: payload.meta
418
+ })?.text?.trim();
419
+ if (line) return line;
420
+ const head = payload.name?.trim() || payload.title?.trim();
421
+ const text = payload.progressText?.trim() || payload.summary?.trim();
422
+ if (head && text) return `**${head}**\n\n${text}`;
423
+ if (text) return text;
424
+ if (head) return head;
425
+ return payload.status?.trim() || payload.kind?.trim() || "";
426
+ }
427
+ /**
428
+ * Creates a per-turn activity publisher. Publishing is best-effort: transport
429
+ * failures are reported through `onError` and never interrupt the reply turn.
430
+ */
431
+ function createClickClackActivityPublisher(params) {
432
+ const flushMs = params.flushMs ?? CLICKCLACK_COMMENTARY_FLUSH_MS;
433
+ const commentaryByItem = /* @__PURE__ */ new Map();
434
+ const toolRows = /* @__PURE__ */ new Map();
435
+ let provenance;
436
+ let chain = Promise.resolve();
437
+ const enqueue = (work) => {
438
+ chain = chain.then(work).catch((error) => {
439
+ params.onError?.(error);
440
+ });
441
+ return chain;
442
+ };
443
+ const postRow = (kind, body) => params.client.createActivityMessage({
444
+ channelId: params.target.channelId,
445
+ conversationId: params.target.conversationId,
446
+ body,
447
+ kind,
448
+ turnId: params.turnId,
449
+ provenance
450
+ });
451
+ const flushCommentary = (segmentKey) => {
452
+ const segment = commentaryByItem.get(segmentKey);
453
+ if (!segment) return Promise.resolve();
454
+ if (segment.timer) {
455
+ clearTimeout(segment.timer);
456
+ segment.timer = void 0;
457
+ }
458
+ if (!segment.dirty || !segment.body.trim()) return Promise.resolve();
459
+ segment.dirty = false;
460
+ const body = segment.body;
461
+ return enqueue(async () => {
462
+ if (segment.messageId) {
463
+ await params.client.updateMessageBody(segment.messageId, body);
464
+ return;
465
+ }
466
+ const posted = await postRow("agent_commentary", body);
467
+ segment.messageId = posted.id;
468
+ });
469
+ };
470
+ const flushAllCommentary = () => {
471
+ const flushes = [...commentaryByItem.keys()].map((key) => flushCommentary(key));
472
+ return Promise.all(flushes).then(() => void 0);
473
+ };
474
+ const handleCommentary = (payload) => {
475
+ const body = commentaryBody(payload);
476
+ if (!body.trim()) return;
477
+ const key = payload.itemId?.trim() || "turn";
478
+ let segment = commentaryByItem.get(key);
479
+ if (!segment) {
480
+ segment = {
481
+ body: "",
482
+ dirty: false
483
+ };
484
+ commentaryByItem.set(key, segment);
485
+ }
486
+ if (body.length < segment.body.length || body === segment.body) return;
487
+ segment.body = body;
488
+ segment.dirty = true;
489
+ if (!segment.timer) segment.timer = setTimeout(() => {
490
+ segment.timer = void 0;
491
+ flushCommentary(key);
492
+ }, flushMs);
493
+ };
494
+ const toolRowKey = (payload) => {
495
+ const toolCallId = payload.toolCallId?.trim();
496
+ if (toolCallId) return toolCallId;
497
+ return (payload.itemId?.trim() ?? "").replace(/^(tool|command):/, "");
498
+ };
499
+ const handleDiscreteItem = (payload) => {
500
+ const body = activityBody(payload);
501
+ if (!body) return;
502
+ const kind = TOOL_ITEM_KINDS.has(payload.kind?.trim().toLowerCase() ?? "") ? "agent_tool" : "agent_commentary";
503
+ flushAllCommentary();
504
+ const key = `${kind}:${toolRowKey(payload)}`;
505
+ const existing = toolRows.get(key);
506
+ if (!existing || !toolRowKey(payload)) {
507
+ const row = { body };
508
+ if (toolRowKey(payload)) toolRows.set(key, row);
509
+ enqueue(async () => {
510
+ const posted = await postRow(kind, row.body);
511
+ row.messageId = posted.id;
512
+ row.sentBody = row.body;
513
+ });
514
+ return;
515
+ }
516
+ if (body.length <= existing.body.length) return;
517
+ existing.body = body;
518
+ enqueue(async () => {
519
+ if (existing.messageId && existing.body !== existing.sentBody) {
520
+ await params.client.updateMessageBody(existing.messageId, existing.body);
521
+ existing.sentBody = existing.body;
522
+ }
523
+ });
524
+ };
525
+ return {
526
+ onItemEvent: (payload) => {
527
+ const kind = normalizedItemKind(payload);
528
+ if (STREAMING_COMMENTARY_ITEM_KINDS.has(kind)) {
529
+ handleCommentary(payload);
530
+ return;
531
+ }
532
+ if (SKIPPED_ITEM_KINDS.has(kind)) return;
533
+ handleDiscreteItem(payload);
534
+ },
535
+ setProvenance: (next) => {
536
+ provenance = next;
537
+ },
538
+ finalize: async () => {
539
+ await flushAllCommentary();
540
+ await chain;
541
+ }
542
+ };
543
+ }
544
+ //#endregion
300
545
  //#region extensions/clickclack/src/resolve.ts
301
546
  /**
302
547
  * Resolves a workspace slug/name/id from config to a ClickClack workspace id.
@@ -384,9 +629,9 @@ async function sendClickClackText(params) {
384
629
  const parsed = parseClickClackTarget(params.to);
385
630
  const explicitThreadId = params.threadId == null ? "" : String(params.threadId);
386
631
  const replyToId = params.replyToId == null ? "" : String(params.replyToId);
387
- if (explicitThreadId || replyToId || parsed.kind === "thread") {
388
- const rootId = explicitThreadId || replyToId || parsed.id;
389
- const message = await client.createThreadReply(rootId, params.text);
632
+ if (explicitThreadId || parsed.kind === "thread") {
633
+ const rootId = explicitThreadId || parsed.id;
634
+ const message = await client.createThreadReply(rootId, params.text, { provenance: params.provenance });
390
635
  return {
391
636
  to: params.to,
392
637
  messageId: message.id
@@ -394,14 +639,17 @@ async function sendClickClackText(params) {
394
639
  }
395
640
  if (parsed.kind === "dm") {
396
641
  const dm = await client.createDirectConversation(workspaceId, [parsed.id]);
397
- const message = await client.createDirectMessage(dm.id, params.text);
642
+ const message = await client.createDirectMessage(dm.id, params.text, { quotedMessageId: replyToId || void 0 });
398
643
  return {
399
644
  to: params.to,
400
645
  messageId: message.id
401
646
  };
402
647
  }
403
648
  const channelId = await resolveChannelId(client, workspaceId, parsed.id);
404
- const message = await client.createChannelMessage(channelId, params.text);
649
+ const message = await client.createChannelMessage(channelId, params.text, {
650
+ provenance: params.provenance,
651
+ quotedMessageId: replyToId || void 0
652
+ });
405
653
  return {
406
654
  to: params.to,
407
655
  messageId: message.id
@@ -421,20 +669,36 @@ function resolveAccountAgentRoute(params) {
421
669
  id: params.target
422
670
  }
423
671
  });
424
- const agentId = params.account.agentId ?? route.agentId;
672
+ const agentId = normalizeAgentId(params.account.agentId ?? route.agentId);
425
673
  if (agentId === route.agentId) return route;
674
+ const peer = {
675
+ kind: params.isDirect ? "direct" : "channel",
676
+ id: params.target
677
+ };
678
+ const dmScope = params.cfg.session?.dmScope ?? "main";
679
+ const sessionKey = runtime.channel.routing.buildAgentSessionKey({
680
+ agentId,
681
+ mainKey: params.cfg.session?.mainKey,
682
+ channel: CHANNEL_ID$1,
683
+ accountId: params.account.accountId,
684
+ peer,
685
+ dmScope,
686
+ identityLinks: params.cfg.session?.identityLinks
687
+ });
688
+ const mainSessionKey = runtime.channel.routing.buildAgentSessionKey({
689
+ agentId,
690
+ mainKey: params.cfg.session?.mainKey,
691
+ channel: CHANNEL_ID$1,
692
+ accountId: params.account.accountId,
693
+ dmScope: "main"
694
+ });
426
695
  return {
427
696
  ...route,
428
697
  agentId,
429
- sessionKey: runtime.channel.routing.buildAgentSessionKey({
430
- agentId,
431
- channel: CHANNEL_ID$1,
432
- accountId: params.account.accountId,
433
- peer: {
434
- kind: params.isDirect ? "direct" : "channel",
435
- id: params.target
436
- }
437
- })
698
+ dmScope,
699
+ sessionKey,
700
+ mainSessionKey,
701
+ lastRoutePolicy: sessionKey === mainSessionKey ? "main" : "session"
438
702
  };
439
703
  }
440
704
  async function dispatchModelReply(params) {
@@ -498,6 +762,22 @@ async function handleClickClackInbound(params) {
498
762
  });
499
763
  return;
500
764
  }
765
+ let turnProvenance;
766
+ let activity;
767
+ if (params.account.agentActivity && (message.channel_id || message.direct_conversation_id)) activity = createClickClackActivityPublisher({
768
+ client: createClickClackClient({
769
+ baseUrl: params.account.baseUrl,
770
+ token: params.account.token
771
+ }),
772
+ target: message.channel_id ? { channelId: message.channel_id } : { conversationId: message.direct_conversation_id },
773
+ turnId: message.id,
774
+ onError: (error) => {
775
+ runtime.logging.getChildLogger({
776
+ plugin: "clickclack",
777
+ feature: "agent-activity"
778
+ }).warn(`clickclack activity publish failed: ${String(error)}`);
779
+ }
780
+ });
501
781
  const senderName = message.author?.display_name || message.author_id;
502
782
  const previousTimestamp = runtime.channel.session.readSessionUpdatedAt({
503
783
  storePath: runtime.channel.session.resolveStorePath(params.config.session?.store, { agentId: route.agentId }),
@@ -540,7 +820,7 @@ async function handleClickClackInbound(params) {
540
820
  OriginatingTo: target,
541
821
  CommandAuthorized: access.commandAuthorized
542
822
  });
543
- await runtime.channel.inbound.dispatchReply({
823
+ const dispatchPromise = runtime.channel.inbound.dispatchReply({
544
824
  cfg: params.config,
545
825
  channel: CHANNEL_ID$1,
546
826
  accountId: params.account.accountId,
@@ -551,6 +831,19 @@ async function handleClickClackInbound(params) {
551
831
  recordInboundSession: runtime.channel.session.recordInboundSession,
552
832
  dispatchReplyWithBufferedBlockDispatcher: runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
553
833
  toolsAllow: params.account.toolsAllow,
834
+ replyOptions: activity ? {
835
+ onModelSelected: (ctx) => {
836
+ turnProvenance = {
837
+ model: ctx.provider && ctx.model ? `${ctx.provider}/${ctx.model}` : ctx.model,
838
+ thinking: ctx.thinkLevel
839
+ };
840
+ activity?.setProvenance(turnProvenance);
841
+ },
842
+ onItemEvent: activity.onItemEvent,
843
+ commentaryProgressEnabled: true,
844
+ suppressDefaultToolProgressMessages: true,
845
+ allowProgressCallbacksWhenSourceDeliverySuppressed: true
846
+ } : void 0,
554
847
  delivery: {
555
848
  deliver: async (payload) => {
556
849
  const text = payload && typeof payload === "object" && "text" in payload ? payload.text ?? "" : "";
@@ -561,7 +854,8 @@ async function handleClickClackInbound(params) {
561
854
  to: target,
562
855
  text,
563
856
  threadId: message.parent_message_id ? message.thread_root_id : void 0,
564
- replyToId: message.id
857
+ replyToId: message.id,
858
+ provenance: turnProvenance
565
859
  });
566
860
  },
567
861
  onError: (error) => {
@@ -573,6 +867,11 @@ async function handleClickClackInbound(params) {
573
867
  throw error instanceof Error ? error : /* @__PURE__ */ new Error(`clickclack session record failed: ${String(error)}`);
574
868
  } }
575
869
  });
870
+ try {
871
+ await dispatchPromise;
872
+ } finally {
873
+ await activity?.finalize();
874
+ }
576
875
  }
577
876
  //#endregion
578
877
  //#region extensions/clickclack/src/gateway.ts
@@ -702,7 +1001,7 @@ async function startClickClackGatewayAccount(ctx) {
702
1001
  event,
703
1002
  botUserId: account.botUserId ?? ""
704
1003
  });
705
- })().catch(reject);
1004
+ })().catch((e) => reject(e instanceof Error ? e : new Error(`ClickClack ws message failed: ${formatErrorMessage(e)}`, { cause: e })));
706
1005
  });
707
1006
  socket.on("close", finishSocketCycle);
708
1007
  socket.on("error", (error) => {
@@ -814,6 +1113,7 @@ const clickClackPlugin = createChatChannelPlugin({
814
1113
  agentId,
815
1114
  channel: CHANNEL_ID,
816
1115
  accountId,
1116
+ recipientSessionExact: parsed.kind === "dm",
817
1117
  peer: {
818
1118
  kind: parsed.chatType === "direct" ? "direct" : "channel",
819
1119
  id: buildClickClackTarget(parsed)
@@ -825,6 +1125,7 @@ const clickClackPlugin = createChatChannelPlugin({
825
1125
  replyToId,
826
1126
  threadId: threadId ?? (parsed.kind === "thread" ? parsed.id : void 0),
827
1127
  currentSessionKey,
1128
+ useSuffix: false,
828
1129
  canRecoverCurrentThread: () => true
829
1130
  });
830
1131
  },
@@ -1,2 +1,2 @@
1
- import { t as clickClackPlugin } from "./channel-OEzEDUUh.js";
1
+ import { t as clickClackPlugin } from "./channel-1ZnJZTPl.js";
2
2
  export { clickClackPlugin };
@@ -1,3 +1,3 @@
1
- import { d as resolveClickClackAccount, i as createClickClackClient, o as setClickClackRuntime, r as parseClickClackTarget } from "./channel-OEzEDUUh.js";
1
+ import { d as resolveClickClackAccount, i as createClickClackClient, o as setClickClackRuntime, r as parseClickClackTarget } from "./channel-1ZnJZTPl.js";
2
2
  import "./api.js";
3
3
  export { createClickClackClient, parseClickClackTarget, resolveClickClackAccount, setClickClackRuntime };
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@openclaw/clickclack",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/clickclack",
9
- "version": "2026.7.1-beta.1",
9
+ "version": "2026.7.1-beta.4",
10
10
  "dependencies": {
11
11
  "ws": "8.21.0",
12
12
  "zod": "4.4.3"
13
13
  },
14
14
  "peerDependencies": {
15
- "openclaw": ">=2026.7.1-beta.1"
15
+ "openclaw": ">=2026.7.1-beta.4"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "openclaw": {
@@ -157,6 +157,9 @@
157
157
  "minimum": 100,
158
158
  "maximum": 60000
159
159
  },
160
+ "agentActivity": {
161
+ "type": "boolean"
162
+ },
160
163
  "accounts": {
161
164
  "type": "object",
162
165
  "propertyNames": {
@@ -299,6 +302,9 @@
299
302
  "type": "integer",
300
303
  "minimum": 100,
301
304
  "maximum": 60000
305
+ },
306
+ "agentActivity": {
307
+ "type": "boolean"
302
308
  }
303
309
  },
304
310
  "additionalProperties": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/clickclack",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.4",
4
4
  "description": "OpenClaw ClickClack channel plugin",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.7.1-beta.1"
16
+ "openclaw": ">=2026.7.1-beta.4"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -49,10 +49,10 @@
49
49
  "allowInvalidConfigRecovery": true
50
50
  },
51
51
  "compat": {
52
- "pluginApi": ">=2026.7.1-beta.1"
52
+ "pluginApi": ">=2026.7.1-beta.4"
53
53
  },
54
54
  "build": {
55
- "openclawVersion": "2026.7.1-beta.1",
55
+ "openclawVersion": "2026.7.1-beta.4",
56
56
  "bundledDist": false
57
57
  },
58
58
  "release": {