@clawling/clawchat-plugin-openclaw 2026.5.12-32 → 2026.5.12-38

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.
@@ -249,7 +249,7 @@ export function createOpenclawClawlingApiClient(opts) {
249
249
  headers: { "content-type": "application/json" },
250
250
  });
251
251
  },
252
- async agentsConnect({ code: inviteCode, platform, type }) {
252
+ async agentsConnect({ code: inviteCode, platform, type, user_id: userId }) {
253
253
  if (!inviteCode?.trim()) {
254
254
  throw new ClawlingApiError("validation", "agentsConnect: inviteCode is required");
255
255
  }
@@ -259,14 +259,18 @@ export function createOpenclawClawlingApiClient(opts) {
259
259
  if (!type?.trim()) {
260
260
  throw new ClawlingApiError("validation", "agentsConnect: type is required");
261
261
  }
262
+ const body = {
263
+ code: inviteCode.trim(),
264
+ platform: platform.trim(),
265
+ type: type.trim(),
266
+ };
267
+ if (userId?.trim()) {
268
+ body.user_id = userId.trim();
269
+ }
262
270
  return await call("POST", "/v1/agents/connect", {
263
271
  // `X-Device-Id` is added globally via `authHeaders` on every request.
264
272
  headers: { "content-type": "application/json" },
265
- body: JSON.stringify({
266
- code: inviteCode.trim(),
267
- platform: platform.trim(),
268
- type: type.trim(),
269
- }),
273
+ body: JSON.stringify(body),
270
274
  });
271
275
  },
272
276
  async uploadMedia(params) {
@@ -1,9 +1,17 @@
1
+ import { createHash } from "node:crypto";
2
+ import os from "node:os";
1
3
  import { createClawChatClient } from "./ws-client.js";
4
+ import { CHANNEL_ID } from "./config.js";
5
+ export function resolveOpenclawClawlingDeviceId(account) {
6
+ const material = [CHANNEL_ID, account.accountId, account.userId, os.hostname()].join("\0");
7
+ const digest = createHash("sha256").update(material).digest("hex").slice(0, 24);
8
+ return `${CHANNEL_ID}-${digest}`;
9
+ }
2
10
  export function createOpenclawClawlingClient(account, overrides = {}) {
3
11
  const client = createClawChatClient({
4
12
  url: account.websocketUrl,
5
13
  token: account.token,
6
- deviceId: account.userId,
14
+ deviceId: resolveOpenclawClawlingDeviceId(account),
7
15
  ...(overrides.transport ? { transport: overrides.transport } : {}),
8
16
  reconnect: {
9
17
  enabled: true,
@@ -66,7 +66,7 @@ function persistOutputVisibility(draft, chatId, outputVisibility) {
66
66
  function formatOutputVisibilityResult(outputVisibility) {
67
67
  const detailLevel = {
68
68
  minimal: "final only",
69
- normal: "final plus block media",
69
+ normal: "final plus block output",
70
70
  full: "final plus buffered reasoning, tool/progress, and block output",
71
71
  };
72
72
  return [
@@ -139,10 +139,12 @@ export async function runOpenclawClawlingLogin(params) {
139
139
  runtime.log("Verifying invite code …");
140
140
  let result;
141
141
  try {
142
+ const existingUserId = account.userId.trim();
142
143
  result = await apiClient.agentsConnect({
143
144
  code: inviteCode,
144
145
  platform: AGENTS_CONNECT_PLATFORM,
145
146
  type: AGENTS_CONNECT_TYPE,
147
+ ...(existingUserId ? { user_id: existingUserId } : {}),
146
148
  });
147
149
  }
148
150
  catch (err) {
@@ -104,17 +104,162 @@ function resolvePayloadText(payload) {
104
104
  return payload.text ?? "";
105
105
  return renderMessagePresentationFallbackText({ presentation, text: payload.text ?? null });
106
106
  }
107
+ const FULL_OUTPUT_SUMMARY_MAX = 600;
108
+ function truncateSummary(text) {
109
+ const compact = text.replace(/\s+/g, " ").trim();
110
+ if (compact.length <= FULL_OUTPUT_SUMMARY_MAX)
111
+ return compact;
112
+ return `${compact.slice(0, FULL_OUTPUT_SUMMARY_MAX - 1).trimEnd()}…`;
113
+ }
114
+ function summarizeValue(value) {
115
+ if (value == null)
116
+ return "";
117
+ if (typeof value === "string") {
118
+ const trimmed = value.trim();
119
+ if (!trimmed)
120
+ return "";
121
+ try {
122
+ const parsed = JSON.parse(trimmed);
123
+ if (parsed && typeof parsed === "object")
124
+ return summarizeValue(parsed);
125
+ }
126
+ catch {
127
+ // Plain text is already the best summary.
128
+ }
129
+ return truncateSummary(trimmed);
130
+ }
131
+ if (typeof value === "number" || typeof value === "boolean")
132
+ return String(value);
133
+ if (Array.isArray(value))
134
+ return `${value.length} item${value.length === 1 ? "" : "s"}`;
135
+ if (typeof value === "object") {
136
+ const keys = Object.keys(value).slice(0, 6);
137
+ return keys.length ? `object with ${keys.join(", ")}` : "object";
138
+ }
139
+ return truncateSummary(String(value));
140
+ }
141
+ function readStringField(payload, field) {
142
+ const value = payload[field];
143
+ return typeof value === "string" ? value.trim() : "";
144
+ }
145
+ function readPayloadCommand(payload) {
146
+ const args = payload.args;
147
+ if (!args || typeof args !== "object" || Array.isArray(args))
148
+ return "";
149
+ const command = args.command;
150
+ return typeof command === "string" ? command.trim() : "";
151
+ }
152
+ function normalizeCommandLabel(value) {
153
+ const trimmed = value.trim();
154
+ return trimmed.replace(/^command\s+/i, "").trim() || trimmed;
155
+ }
156
+ function isTerminalCommandOutput(payload) {
157
+ const phase = readStringField(payload, "phase").toLowerCase();
158
+ const status = readStringField(payload, "status").toLowerCase();
159
+ return (phase === "end" ||
160
+ phase === "error" ||
161
+ typeof payload.exitCode === "number" ||
162
+ status === "completed" ||
163
+ status === "ok" ||
164
+ status === "success" ||
165
+ status === "failed" ||
166
+ status === "error");
167
+ }
168
+ function isToolProgressItem(payload) {
169
+ const kind = readStringField(payload, "kind").toLowerCase();
170
+ const title = readStringField(payload, "title").toLowerCase();
171
+ const name = readStringField(payload, "name").toLowerCase();
172
+ const progressText = readStringField(payload, "progressText").toLowerCase();
173
+ return (kind === "tool" ||
174
+ kind === "command" ||
175
+ title.startsWith("exec ") ||
176
+ title.startsWith("command ") ||
177
+ name.startsWith("exec ") ||
178
+ name.startsWith("command ") ||
179
+ progressText.startsWith("exec ") ||
180
+ progressText.startsWith("command "));
181
+ }
182
+ function isDefaultToolResultText(text) {
183
+ return /^[🛠🔧]/u.test(text.trim());
184
+ }
185
+ function summarizeProgressPayload(payload) {
186
+ return (readStringField(payload, "progressText") ||
187
+ readStringField(payload, "summary") ||
188
+ readStringField(payload, "message") ||
189
+ readStringField(payload, "title") ||
190
+ readStringField(payload, "name") ||
191
+ readStringField(payload, "status") ||
192
+ summarizeValue(payload));
193
+ }
194
+ function formatToolStartSummary(payload) {
195
+ const name = payload.name?.trim() || "tool";
196
+ const phase = payload.phase?.trim();
197
+ if (phase && phase !== "start")
198
+ return "";
199
+ const command = readPayloadCommand(payload);
200
+ if ((name === "exec" || name === "command") && !command)
201
+ return "";
202
+ return `[tool] ${[name, command].filter(Boolean).join(" ")} started`;
203
+ }
204
+ function formatCommandOutputSummary(payload) {
205
+ if (!isTerminalCommandOutput(payload))
206
+ return "";
207
+ const name = normalizeCommandLabel(readStringField(payload, "title") || readStringField(payload, "name") || "command");
208
+ const status = readStringField(payload, "status").toLowerCase();
209
+ const visibleStatus = status && status !== "ok" && status !== "completed" && status !== "success" ? status : "";
210
+ const exitCode = typeof payload.exitCode === "number" ? ` exit ${payload.exitCode}` : "";
211
+ const output = summarizeValue(payload.output);
212
+ const prefix = `[command] ${[name, visibleStatus].filter(Boolean).join(" ")}${exitCode}`;
213
+ return truncateSummary(output ? `${prefix}: ${output}` : prefix);
214
+ }
215
+ function formatPatchSummary(payload) {
216
+ const summary = readStringField(payload, "summary");
217
+ if (summary)
218
+ return `[patch] ${truncateSummary(summary)}`;
219
+ const parts = [];
220
+ for (const key of ["added", "modified", "deleted"]) {
221
+ const value = payload[key];
222
+ if (Array.isArray(value) && value.length > 0)
223
+ parts.push(`${key}: ${value.map(String).join(", ")}`);
224
+ else if (typeof value === "number" && value > 0)
225
+ parts.push(`${key}: ${value}`);
226
+ }
227
+ return `[patch] ${truncateSummary(parts.join("; ") || "updated")}`;
228
+ }
229
+ function formatItemEventSummary(payload) {
230
+ const kind = readStringField(payload, "kind") || "progress";
231
+ const title = readStringField(payload, "title") || readStringField(payload, "name");
232
+ const status = readStringField(payload, "status");
233
+ const phase = readStringField(payload, "phase");
234
+ const summary = summarizeProgressPayload(payload);
235
+ const label = [title, status || phase].filter(Boolean).join(" ");
236
+ return `[${kind}] ${truncateSummary(label || summary || "activity")}`;
237
+ }
238
+ function formatPlanSummary(payload) {
239
+ const title = readStringField(payload, "title") || "plan";
240
+ const explanation = readStringField(payload, "explanation");
241
+ const steps = Array.isArray(payload.steps) ? payload.steps.map(String).filter(Boolean) : [];
242
+ return `[plan] ${truncateSummary([title, explanation, ...steps].filter(Boolean).join(": "))}`;
243
+ }
244
+ function formatApprovalSummary(payload) {
245
+ const title = readStringField(payload, "title") || readStringField(payload, "kind") || "approval";
246
+ const status = readStringField(payload, "status") || readStringField(payload, "phase");
247
+ const message = readStringField(payload, "message") || readStringField(payload, "reason");
248
+ return `[approval] ${truncateSummary([title, status, message].filter(Boolean).join(" "))}`;
249
+ }
107
250
  /**
108
251
  * Reply dispatcher for clawchat-plugin-openclaw.
109
252
  *
110
253
  * ClawChat emits only materialized `message.send` / `message.reply` frames for
111
- * the final reply. Non-final OpenClaw deliveries are ignored or buffered
112
- * according to outputVisibility and are never sent as separate ClawChat frames.
254
+ * complete OpenClaw output units. `disableBlockStreaming` prevents token/block
255
+ * streaming; full visibility still forwards complete tool/progress/output units
256
+ * as separate ClawChat messages.
113
257
  */
114
258
  export function createOpenclawClawlingReplyDispatcher(options) {
115
259
  const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, log, } = options;
116
260
  const isGroupTarget = target.chatType === "group";
117
261
  const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
262
+ const splitFullOutput = outputVisibility === "full" && !isGroupTarget;
118
263
  const ownerDirectTarget = () => {
119
264
  const ownerUserId = account.ownerUserId?.trim();
120
265
  return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
@@ -143,10 +288,13 @@ export function createOpenclawClawlingReplyDispatcher(options) {
143
288
  // ----- Reply state ------------------------------------------------------
144
289
  let reasoningText = "";
145
290
  let bufferedOutputText = "";
291
+ const bufferedOutputLineSet = new Set();
292
+ const emittedFullSegmentSet = new Set();
146
293
  const bufferedOutputUrls = [];
147
294
  let runDone = false;
148
295
  let typingActive = false;
149
296
  let terminalReplySuppressed = false;
297
+ let finalDeliverySeen = false;
150
298
  const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
151
299
  const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
152
300
  const terminalSendScopeId = options.terminalSendScopeId ?? null;
@@ -231,14 +379,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
231
379
  recordOutbound("thinking", messageId, thinkingText);
232
380
  reasoningText = "";
233
381
  };
234
- const resetBufferedOutput = () => {
235
- bufferedOutputText = "";
236
- bufferedOutputUrls.length = 0;
237
- };
238
382
  const appendBufferedText = (value) => {
239
383
  const trimmed = value.trim();
240
384
  if (!trimmed)
241
385
  return;
386
+ if (!trimmed.includes("\n")) {
387
+ if (bufferedOutputLineSet.has(trimmed))
388
+ return;
389
+ bufferedOutputLineSet.add(trimmed);
390
+ }
242
391
  bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
243
392
  };
244
393
  const appendBufferedUrls = (urls) => {
@@ -247,13 +396,17 @@ export function createOpenclawClawlingReplyDispatcher(options) {
247
396
  bufferedOutputUrls.push(url);
248
397
  }
249
398
  };
399
+ const fullSegmentKey = (text, urls) => JSON.stringify({
400
+ text: text.replace(/\s+/g, " ").trim(),
401
+ urls: urls.filter(Boolean),
402
+ });
250
403
  const mergeFinalText = (text) => {
251
- if (outputVisibility !== "full")
404
+ if (outputVisibility === "minimal" || outputVisibility === "full")
252
405
  return text;
253
406
  return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
254
407
  };
255
408
  const mergeFinalUrls = (urls) => {
256
- if (outputVisibility === "minimal")
409
+ if (outputVisibility === "minimal" || outputVisibility === "full")
257
410
  return urls;
258
411
  const merged = bufferedOutputUrls.slice();
259
412
  for (const url of urls) {
@@ -369,14 +522,38 @@ export function createOpenclawClawlingReplyDispatcher(options) {
369
522
  });
370
523
  return result;
371
524
  };
525
+ const emitFullSegment = async (text, urls = []) => {
526
+ if (outputVisibility !== "full") {
527
+ appendBufferedText(text);
528
+ appendBufferedUrls(urls);
529
+ return;
530
+ }
531
+ if (!splitFullOutput) {
532
+ appendBufferedText(text);
533
+ appendBufferedUrls(urls);
534
+ return;
535
+ }
536
+ const trimmed = text.trim();
537
+ if (!trimmed && urls.length === 0)
538
+ return;
539
+ const segmentKey = fullSegmentKey(trimmed, urls);
540
+ if (emittedFullSegmentSet.has(segmentKey))
541
+ return;
542
+ emittedFullSegmentSet.add(segmentKey);
543
+ const mediaFragments = await uploadMediaUrls(urls);
544
+ await sendStatic(trimmed, mediaFragments, [], { recordMessage: true });
545
+ };
546
+ const emitFullRuntimeText = async (label, text, urls = []) => {
547
+ const summary = summarizeValue(text);
548
+ if (!summary && urls.length === 0)
549
+ return;
550
+ await emitFullSegment(summary ? `[${label}] ${summary}` : "", urls);
551
+ };
372
552
  // ----- Dispatcher -------------------------------------------------------
373
553
  const base = runtime.channel.reply.createReplyDispatcherWithTyping({
374
554
  humanDelay,
375
555
  onReplyStart: async () => {
376
556
  emitTyping(true);
377
- reasoningText = "";
378
- resetBufferedOutput();
379
- runDone = false;
380
557
  },
381
558
  deliver: async (payload, info) => {
382
559
  if (consumeTerminalSend(info?.kind ?? "unknown"))
@@ -386,41 +563,47 @@ export function createOpenclawClawlingReplyDispatcher(options) {
386
563
  const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
387
564
  log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw deliver kind=${info?.kind ?? "unknown"} text_len=${text.length} media_urls=${urls.length} reasoning=${payload.isReasoning === true}`);
388
565
  if (isGroupTarget && richFragment) {
566
+ if (info?.kind === "final")
567
+ finalDeliverySeen = true;
389
568
  if (info?.kind !== "final")
390
569
  return;
391
570
  await sendOwnerAttention(resolvePayloadText(payload), richFragment);
392
571
  return;
393
572
  }
394
573
  if (isGroupTarget && info?.kind === "final" && looksLikeApprovalFallbackText(text)) {
574
+ finalDeliverySeen = true;
395
575
  await sendOwnerAttention(text);
396
576
  return;
397
577
  }
398
578
  if (payload.isReasoning) {
399
- if (isGroupTarget || outputVisibility !== "full")
579
+ if (outputVisibility !== "full")
400
580
  return;
401
- appendBufferedText(text);
581
+ await emitFullSegment(text, urls);
402
582
  const trimmed = text.trim();
403
583
  if (trimmed)
404
584
  reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
405
585
  return;
406
586
  }
407
587
  if (info?.kind === "tool") {
408
- if (!isGroupTarget && outputVisibility === "full") {
409
- appendBufferedText(text);
410
- appendBufferedUrls(urls);
588
+ if (isDefaultToolResultText(text))
589
+ return;
590
+ if (outputVisibility === "full") {
591
+ await emitFullRuntimeText("tool result", text, urls);
411
592
  }
412
593
  return;
413
594
  }
414
595
  if (info?.kind === "block") {
415
- if (!isGroupTarget && outputVisibility === "normal")
416
- appendBufferedUrls(urls);
417
- if (!isGroupTarget && outputVisibility === "full") {
596
+ if (outputVisibility === "full") {
597
+ await emitFullSegment(text, urls);
598
+ }
599
+ else if (outputVisibility === "minimal" || outputVisibility === "normal") {
418
600
  appendBufferedText(text);
419
601
  appendBufferedUrls(urls);
420
602
  }
421
603
  return;
422
604
  }
423
605
  if (info?.kind === "final") {
606
+ finalDeliverySeen = true;
424
607
  if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
425
608
  log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
426
609
  openclawLlmContextDebug.writeSnapshot({
@@ -455,6 +638,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
455
638
  onError: (error, info) => {
456
639
  const errorText = normalizeReplyErrorText(error);
457
640
  log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw ${info.kind} reply failed: ${errorText}`);
641
+ if (!isGroupTarget && outputVisibility === "full")
642
+ void emitFullRuntimeText("error", errorText);
458
643
  if (isGroupTarget) {
459
644
  log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group runtime failure suppressed from ClawChat clients group=${target.chatId}`);
460
645
  return;
@@ -465,6 +650,16 @@ export function createOpenclawClawlingReplyDispatcher(options) {
465
650
  if (runDone)
466
651
  return;
467
652
  runDone = true;
653
+ if (finalDeliverySeen)
654
+ return;
655
+ const fallbackText = bufferedOutputText.trim();
656
+ const fallbackUrls = bufferedOutputUrls.slice();
657
+ if (!fallbackText && fallbackUrls.length === 0)
658
+ return;
659
+ const mediaFragments = await uploadMediaUrls(fallbackUrls);
660
+ const result = await sendStatic(fallbackText, mediaFragments, [], { recordMessage: true });
661
+ if (result?.messageId)
662
+ recordThinkingIfLinked(result.messageId);
468
663
  },
469
664
  onCleanup: () => {
470
665
  emitTyping(false);
@@ -475,7 +670,81 @@ export function createOpenclawClawlingReplyDispatcher(options) {
475
670
  replyOptions: {
476
671
  ...base.replyOptions,
477
672
  sourceReplyDeliveryMode: "automatic",
478
- disableBlockStreaming: outputVisibility !== "full",
673
+ disableBlockStreaming: true,
674
+ suppressDefaultToolProgressMessages: true,
675
+ allowProgressCallbacksWhenSourceDeliverySuppressed: outputVisibility === "full" ? true : undefined,
676
+ onReasoningStream: outputVisibility === "full"
677
+ ? async (payload) => {
678
+ if (consumeTerminalSend("reasoning"))
679
+ return;
680
+ const text = resolvePayloadText(payload);
681
+ await emitFullRuntimeText("reasoning", text, resolveOutboundMediaUrls(payload).filter(Boolean));
682
+ const trimmed = text.trim();
683
+ if (trimmed)
684
+ reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
685
+ }
686
+ : undefined,
687
+ onToolStart: outputVisibility === "full"
688
+ ? async (payload) => {
689
+ if (consumeTerminalSend("tool-start"))
690
+ return;
691
+ await emitFullSegment(formatToolStartSummary(payload));
692
+ }
693
+ : undefined,
694
+ onToolResult: outputVisibility === "full"
695
+ ? async (payload) => {
696
+ if (consumeTerminalSend("tool-result"))
697
+ return;
698
+ const text = resolvePayloadText(payload);
699
+ if (isDefaultToolResultText(text))
700
+ return;
701
+ await emitFullRuntimeText("tool result", text, resolveOutboundMediaUrls(payload).filter(Boolean));
702
+ }
703
+ : undefined,
704
+ onItemEvent: outputVisibility === "full"
705
+ ? async (payload) => {
706
+ if (consumeTerminalSend("item-event"))
707
+ return;
708
+ if (isToolProgressItem(payload))
709
+ return;
710
+ await emitFullRuntimeText("progress", summarizeProgressPayload(payload));
711
+ }
712
+ : undefined,
713
+ onPlanUpdate: outputVisibility === "full"
714
+ ? async (payload) => {
715
+ if (consumeTerminalSend("plan-update"))
716
+ return;
717
+ await emitFullRuntimeText("plan", summarizeProgressPayload(payload));
718
+ }
719
+ : undefined,
720
+ onCommandOutput: outputVisibility === "full"
721
+ ? async (payload) => {
722
+ if (consumeTerminalSend("command-output"))
723
+ return;
724
+ await emitFullSegment(formatCommandOutputSummary(payload));
725
+ }
726
+ : undefined,
727
+ onPatchSummary: outputVisibility === "full"
728
+ ? async (payload) => {
729
+ if (consumeTerminalSend("patch-summary"))
730
+ return;
731
+ await emitFullSegment(formatPatchSummary(payload));
732
+ }
733
+ : undefined,
734
+ onCompactionStart: outputVisibility === "full"
735
+ ? async () => {
736
+ if (consumeTerminalSend("compaction-start"))
737
+ return;
738
+ await emitFullSegment("[compaction] started");
739
+ }
740
+ : undefined,
741
+ onCompactionEnd: outputVisibility === "full"
742
+ ? async () => {
743
+ if (consumeTerminalSend("compaction-end"))
744
+ return;
745
+ await emitFullSegment("[compaction] finished");
746
+ }
747
+ : undefined,
479
748
  },
480
749
  markDispatchIdle: base.markDispatchIdle,
481
750
  };
@@ -5,7 +5,7 @@ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
5
5
  import { createOpenclawClawlingClient } from "./client.js";
6
6
  import { createOpenclawClawlingApiClient } from "./api-client.js";
7
7
  import { ClawlingApiError } from "./api-types.js";
8
- import { CHANNEL_ID, effectiveGroupCommandMode, hasOpenclawClawlingConnectCredentials, } from "./config.js";
8
+ import { CHANNEL_ID, effectiveOutputVisibility, effectiveGroupCommandMode, hasOpenclawClawlingConnectCredentials, } from "./config.js";
9
9
  import { dispatchOpenclawClawlingInbound } from "./inbound.js";
10
10
  import { fetchInboundMedia } from "./media-runtime.js";
11
11
  import { createOpenclawClawlingReplyDispatcher } from "./reply-dispatcher.js";
@@ -41,6 +41,38 @@ const OPENCLAW_CONFIRM_SLASH_COMMANDS = new Set([
41
41
  "nevermind",
42
42
  ]);
43
43
  const GROUP_OWNER_ATTENTION_TITLE = "requires owner attention";
44
+ function isRecord(value) {
45
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
46
+ }
47
+ function withFullVerboseDispatchConfig(cfg, agentId) {
48
+ const cfgRecord = cfg;
49
+ const agents = isRecord(cfgRecord.agents) ? cfgRecord.agents : {};
50
+ const defaults = isRecord(agents.defaults) ? agents.defaults : {};
51
+ const nextAgents = {
52
+ ...agents,
53
+ defaults: {
54
+ ...defaults,
55
+ verboseDefault: "full",
56
+ },
57
+ };
58
+ const agentCfg = agents[agentId];
59
+ if (agentId && agentId !== "defaults" && isRecord(agentCfg)) {
60
+ nextAgents[agentId] = {
61
+ ...agentCfg,
62
+ verboseDefault: "full",
63
+ };
64
+ }
65
+ return {
66
+ ...cfgRecord,
67
+ agents: nextAgents,
68
+ };
69
+ }
70
+ function resolveDispatchConfigForOutputVisibility(params) {
71
+ const visibility = effectiveOutputVisibility(params.account, params.chatId, params.chatType);
72
+ return visibility === "full"
73
+ ? withFullVerboseDispatchConfig(params.cfg, params.agentId)
74
+ : params.cfg;
75
+ }
44
76
  function resolveChannelContextBuilder(rt) {
45
77
  const channel = rt;
46
78
  const buildContext = channel.inbound?.buildContext ?? channel.turn?.buildContext;
@@ -1143,6 +1175,13 @@ export async function startOpenclawClawlingGateway(params) {
1143
1175
  }
1144
1176
  const replyCtx = turn.replyCtx;
1145
1177
  const terminalSendScopeId = `${account.accountId}\0${turn.peer.id}\0${turn.messageId}`;
1178
+ const dispatchCfg = resolveDispatchConfigForOutputVisibility({
1179
+ cfg,
1180
+ account,
1181
+ chatId: turn.peer.id,
1182
+ chatType: turn.peer.kind,
1183
+ agentId: route.agentId,
1184
+ });
1146
1185
  const { dispatcher, replyOptions, markDispatchIdle } = createOpenclawClawlingReplyDispatcher({
1147
1186
  cfg,
1148
1187
  runtime,
@@ -1174,7 +1213,7 @@ export async function startOpenclawClawlingGateway(params) {
1174
1213
  const dispatchResult = await rt.reply.withReplyDispatcher({
1175
1214
  dispatcher,
1176
1215
  onSettled: () => markDispatchIdle(),
1177
- run: () => runWithTerminalClawChatSendScope(terminalSendScopeId, () => rt.reply.dispatchReplyFromConfig({ ctx: ctxPayload, cfg, dispatcher, replyOptions })),
1216
+ run: () => runWithTerminalClawChatSendScope(terminalSendScopeId, () => rt.reply.dispatchReplyFromConfig({ ctx: ctxPayload, cfg: dispatchCfg, dispatcher, replyOptions })),
1178
1217
  });
1179
1218
  const counts = dispatchResult?.counts ?? {};
1180
1219
  const queuedFinal = Boolean(dispatchResult?.queuedFinal);
@@ -228,11 +228,6 @@ export const ClawchatUpdateAccountProfileSchema = Type.Object({
228
228
  description: "New self-introduction / bio text for the agent's connected ClawChat account, mirroring the local assistant identity",
229
229
  })),
230
230
  });
231
- export const ClawchatUploadMediaFileSchema = Type.Object({
232
- filePath: Type.String({
233
- description: "Absolute local path of the non-avatar media/file to upload to ClawChat for a ClawChat-accessible URL (max 20MB)",
234
- }),
235
- });
236
231
  export const ClawchatUploadAvatarImageSchema = Type.Object({
237
232
  filePath: Type.String({
238
233
  description: "Absolute local path of the avatar image to upload for the agent's connected ClawChat account (max 20MB)",
package/dist/src/tools.js CHANGED
@@ -9,7 +9,7 @@ import { getOpenclawClawlingClient, } from "./runtime.js";
9
9
  import { markTerminalClawChatSend } from "./terminal-send.js";
10
10
  import { editClawChatMemoryBody, readClawChatMemoryFile, resolveClawChatMemoryPath, searchClawChatMemory, writeClawChatMemoryBody, } from "./clawchat-memory.js";
11
11
  import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, pushMetadata, updateMetadata, } from "./clawchat-metadata.js";
12
- import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatAcceptFriendRequestSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListFriendRequestsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatRejectFriendRequestSchema, ClawchatRemoveFriendSchema, ClawchatSearchUsersSchema, ClawchatSendFriendRequestSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatUploadMediaFileSchema, } from "./tools-schema.js";
12
+ import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatAcceptFriendRequestSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListFriendRequestsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatRejectFriendRequestSchema, ClawchatRemoveFriendSchema, ClawchatSearchUsersSchema, ClawchatSendFriendRequestSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, } from "./tools-schema.js";
13
13
  const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
14
14
  // Owner-approval gate business codes (must match member-backend codes.go).
15
15
  const CODE_PENDING_APPROVAL = 21001;
@@ -997,39 +997,5 @@ export function registerOpenclawClawlingTools(api, options = {}) {
997
997
  });
998
998
  },
999
999
  }, { name: "clawchat_upload_avatar_image" });
1000
- api.registerTool({
1001
- name: "clawchat_upload_media_file",
1002
- label: "Upload ClawChat Media File",
1003
- description: toolDescription("Upload an absolute local file/media path to ClawChat media storage (max 20MB) and return a ClawChat-accessible public/shareable URL. " +
1004
- "TRIGGER — invoke when the user provides an absolute local file path and asks to upload, share, or create a ClawChat-accessible link for that file. " +
1005
- "Do not use this tool to send an attachment in the current chat; use the current runtime's native media-send mechanism instead (for example, MEDIA:/absolute/local/path where supported). " +
1006
- "Do not use this for account avatar changes; use `clawchat_upload_avatar_image` for avatar images. Do not use this just to mirror local assistant identity."),
1007
- parameters: ClawchatUploadMediaFileSchema,
1008
- async execute(_callId, params) {
1009
- return await recordClawchatToolCall("clawchat_upload_media_file", params, async () => {
1010
- const p = params;
1011
- if (!p.filePath || !path.isAbsolute(p.filePath)) {
1012
- return validationError("clawchat-plugin-openclaw: filePath must be an absolute local path");
1013
- }
1014
- let stat;
1015
- try {
1016
- stat = fs.statSync(p.filePath);
1017
- }
1018
- catch (err) {
1019
- return validationError(`clawchat-plugin-openclaw: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`);
1020
- }
1021
- if (!stat.isFile()) {
1022
- return validationError(`clawchat-plugin-openclaw: ${p.filePath} is not a regular file`);
1023
- }
1024
- if (stat.size > MAX_UPLOAD_BYTES) {
1025
- return validationError(`clawchat-plugin-openclaw: file too large (${stat.size} bytes; max 20MB)`);
1026
- }
1027
- const buffer = fs.readFileSync(p.filePath);
1028
- const filename = path.basename(p.filePath);
1029
- const mime = inferMimeFromPath(p.filePath);
1030
- return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
1031
- });
1032
- },
1033
- }, { name: "clawchat_upload_media_file" });
1034
- api.logger.debug?.("clawchat-plugin-openclaw: registered 27 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, send_friend_request, list_friend_requests, accept_friend_request, reject_friend_request, remove_friend, search_users, get_conversation, mention_message, list_moments, create_moment, delete_moment, toggle_moment_reaction, create_moment_comment, reply_moment_comment, delete_moment_comment, update_account_profile, upload_avatar_image, upload_media_file, memory_search, memory_read, memory_write, memory_edit, metadata_sync, metadata_update)");
1000
+ api.logger.debug?.("clawchat-plugin-openclaw: registered 26 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, send_friend_request, list_friend_requests, accept_friend_request, reject_friend_request, remove_friend, search_users, get_conversation, mention_message, list_moments, create_moment, delete_moment, toggle_moment_reaction, create_moment_comment, reply_moment_comment, delete_moment_comment, update_account_profile, upload_avatar_image, memory_search, memory_read, memory_write, memory_edit, metadata_sync, metadata_update)");
1035
1001
  }
@@ -46,7 +46,6 @@
46
46
  "clawchat_delete_moment_comment",
47
47
  "clawchat_update_account_profile",
48
48
  "clawchat_upload_avatar_image",
49
- "clawchat_upload_media_file",
50
49
  "clawchat_memory_search",
51
50
  "clawchat_memory_read",
52
51
  "clawchat_memory_write",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.5.12-32",
3
+ "version": "2026.5.12-38",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -57,7 +57,6 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
57
57
  | Reply to an existing comment | `clawchat_reply_moment_comment` with `replyToCommentId` |
58
58
  | Delete a comment/reply | `clawchat_delete_moment_comment` with exact `momentId` and `commentId` |
59
59
  | Nickname or bio update | `clawchat_update_account_profile` |
60
- | Standalone shareable media URL | `clawchat_upload_media_file` |
61
60
 
62
61
  ## Profile And Identity Sync
63
62