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

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,163 @@ 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";
263
+ const splitNormalBlockOutput = outputVisibility === "normal";
118
264
  const ownerDirectTarget = () => {
119
265
  const ownerUserId = account.ownerUserId?.trim();
120
266
  return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
@@ -143,10 +289,13 @@ export function createOpenclawClawlingReplyDispatcher(options) {
143
289
  // ----- Reply state ------------------------------------------------------
144
290
  let reasoningText = "";
145
291
  let bufferedOutputText = "";
292
+ const bufferedOutputLineSet = new Set();
293
+ const emittedFullSegmentSet = new Set();
146
294
  const bufferedOutputUrls = [];
147
295
  let runDone = false;
148
296
  let typingActive = false;
149
297
  let terminalReplySuppressed = false;
298
+ let finalDeliverySeen = false;
150
299
  const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
151
300
  const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
152
301
  const terminalSendScopeId = options.terminalSendScopeId ?? null;
@@ -231,14 +380,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
231
380
  recordOutbound("thinking", messageId, thinkingText);
232
381
  reasoningText = "";
233
382
  };
234
- const resetBufferedOutput = () => {
235
- bufferedOutputText = "";
236
- bufferedOutputUrls.length = 0;
237
- };
238
383
  const appendBufferedText = (value) => {
239
384
  const trimmed = value.trim();
240
385
  if (!trimmed)
241
386
  return;
387
+ if (!trimmed.includes("\n")) {
388
+ if (bufferedOutputLineSet.has(trimmed))
389
+ return;
390
+ bufferedOutputLineSet.add(trimmed);
391
+ }
242
392
  bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
243
393
  };
244
394
  const appendBufferedUrls = (urls) => {
@@ -247,13 +397,17 @@ export function createOpenclawClawlingReplyDispatcher(options) {
247
397
  bufferedOutputUrls.push(url);
248
398
  }
249
399
  };
400
+ const fullSegmentKey = (text, urls) => JSON.stringify({
401
+ text: text.replace(/\s+/g, " ").trim(),
402
+ urls: urls.filter(Boolean),
403
+ });
250
404
  const mergeFinalText = (text) => {
251
- if (outputVisibility !== "full")
405
+ if (outputVisibility === "minimal" || outputVisibility === "full")
252
406
  return text;
253
407
  return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
254
408
  };
255
409
  const mergeFinalUrls = (urls) => {
256
- if (outputVisibility === "minimal")
410
+ if (outputVisibility === "minimal" || outputVisibility === "full")
257
411
  return urls;
258
412
  const merged = bufferedOutputUrls.slice();
259
413
  for (const url of urls) {
@@ -369,14 +523,38 @@ export function createOpenclawClawlingReplyDispatcher(options) {
369
523
  });
370
524
  return result;
371
525
  };
526
+ const emitFullSegment = async (text, urls = []) => {
527
+ if (outputVisibility !== "full" && !splitNormalBlockOutput) {
528
+ appendBufferedText(text);
529
+ appendBufferedUrls(urls);
530
+ return;
531
+ }
532
+ if (!splitFullOutput && !splitNormalBlockOutput) {
533
+ appendBufferedText(text);
534
+ appendBufferedUrls(urls);
535
+ return;
536
+ }
537
+ const trimmed = text.trim();
538
+ if (!trimmed && urls.length === 0)
539
+ return;
540
+ const segmentKey = fullSegmentKey(trimmed, urls);
541
+ if (emittedFullSegmentSet.has(segmentKey))
542
+ return;
543
+ emittedFullSegmentSet.add(segmentKey);
544
+ const mediaFragments = await uploadMediaUrls(urls);
545
+ await sendStatic(trimmed, mediaFragments, [], { recordMessage: true });
546
+ };
547
+ const emitFullRuntimeText = async (label, text, urls = []) => {
548
+ const summary = summarizeValue(text);
549
+ if (!summary && urls.length === 0)
550
+ return;
551
+ await emitFullSegment(summary ? `[${label}] ${summary}` : "", urls);
552
+ };
372
553
  // ----- Dispatcher -------------------------------------------------------
373
554
  const base = runtime.channel.reply.createReplyDispatcherWithTyping({
374
555
  humanDelay,
375
556
  onReplyStart: async () => {
376
557
  emitTyping(true);
377
- reasoningText = "";
378
- resetBufferedOutput();
379
- runDone = false;
380
558
  },
381
559
  deliver: async (payload, info) => {
382
560
  if (consumeTerminalSend(info?.kind ?? "unknown"))
@@ -386,41 +564,50 @@ export function createOpenclawClawlingReplyDispatcher(options) {
386
564
  const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
387
565
  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
566
  if (isGroupTarget && richFragment) {
567
+ if (info?.kind === "final")
568
+ finalDeliverySeen = true;
389
569
  if (info?.kind !== "final")
390
570
  return;
391
571
  await sendOwnerAttention(resolvePayloadText(payload), richFragment);
392
572
  return;
393
573
  }
394
574
  if (isGroupTarget && info?.kind === "final" && looksLikeApprovalFallbackText(text)) {
575
+ finalDeliverySeen = true;
395
576
  await sendOwnerAttention(text);
396
577
  return;
397
578
  }
398
579
  if (payload.isReasoning) {
399
- if (isGroupTarget || outputVisibility !== "full")
580
+ if (outputVisibility !== "full")
400
581
  return;
401
- appendBufferedText(text);
582
+ await emitFullSegment(text, urls);
402
583
  const trimmed = text.trim();
403
584
  if (trimmed)
404
585
  reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
405
586
  return;
406
587
  }
407
588
  if (info?.kind === "tool") {
408
- if (!isGroupTarget && outputVisibility === "full") {
409
- appendBufferedText(text);
410
- appendBufferedUrls(urls);
589
+ if (isDefaultToolResultText(text))
590
+ return;
591
+ if (outputVisibility === "full") {
592
+ await emitFullRuntimeText("tool result", text, urls);
411
593
  }
412
594
  return;
413
595
  }
414
596
  if (info?.kind === "block") {
415
- if (!isGroupTarget && outputVisibility === "normal")
416
- appendBufferedUrls(urls);
417
- if (!isGroupTarget && outputVisibility === "full") {
597
+ if (outputVisibility === "full") {
598
+ await emitFullSegment(text, urls);
599
+ }
600
+ else if (splitNormalBlockOutput) {
601
+ await emitFullSegment(text, urls);
602
+ }
603
+ else if (outputVisibility === "minimal" || outputVisibility === "normal") {
418
604
  appendBufferedText(text);
419
605
  appendBufferedUrls(urls);
420
606
  }
421
607
  return;
422
608
  }
423
609
  if (info?.kind === "final") {
610
+ finalDeliverySeen = true;
424
611
  if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
425
612
  log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
426
613
  openclawLlmContextDebug.writeSnapshot({
@@ -444,6 +631,12 @@ export function createOpenclawClawlingReplyDispatcher(options) {
444
631
  }
445
632
  const finalText = richFragment && account.richInteractions ? mergeFinalText("") : mergeFinalText(text);
446
633
  const finalUrls = mergeFinalUrls(urls);
634
+ if (isClawChatNoopResponseText(finalText) &&
635
+ !richFragment &&
636
+ finalUrls.length === 0) {
637
+ log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
638
+ return;
639
+ }
447
640
  const mediaFragments = await uploadMediaUrls(finalUrls);
448
641
  const result = await sendStatic(finalText, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
449
642
  if (result?.messageId)
@@ -455,16 +648,24 @@ export function createOpenclawClawlingReplyDispatcher(options) {
455
648
  onError: (error, info) => {
456
649
  const errorText = normalizeReplyErrorText(error);
457
650
  log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw ${info.kind} reply failed: ${errorText}`);
458
- if (isGroupTarget) {
459
- log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group runtime failure suppressed from ClawChat clients group=${target.chatId}`);
460
- return;
461
- }
651
+ if (outputVisibility === "full")
652
+ void emitFullRuntimeText("error", errorText);
462
653
  },
463
654
  onIdle: async () => {
464
655
  emitTyping(false);
465
656
  if (runDone)
466
657
  return;
467
658
  runDone = true;
659
+ if (finalDeliverySeen)
660
+ return;
661
+ const fallbackText = bufferedOutputText.trim();
662
+ const fallbackUrls = bufferedOutputUrls.slice();
663
+ if (!fallbackText && fallbackUrls.length === 0)
664
+ return;
665
+ const mediaFragments = await uploadMediaUrls(fallbackUrls);
666
+ const result = await sendStatic(fallbackText, mediaFragments, [], { recordMessage: true });
667
+ if (result?.messageId)
668
+ recordThinkingIfLinked(result.messageId);
468
669
  },
469
670
  onCleanup: () => {
470
671
  emitTyping(false);
@@ -475,7 +676,81 @@ export function createOpenclawClawlingReplyDispatcher(options) {
475
676
  replyOptions: {
476
677
  ...base.replyOptions,
477
678
  sourceReplyDeliveryMode: "automatic",
478
- disableBlockStreaming: outputVisibility !== "full",
679
+ disableBlockStreaming: !splitNormalBlockOutput,
680
+ suppressDefaultToolProgressMessages: true,
681
+ allowProgressCallbacksWhenSourceDeliverySuppressed: splitFullOutput ? true : undefined,
682
+ onReasoningStream: splitFullOutput
683
+ ? async (payload) => {
684
+ if (consumeTerminalSend("reasoning"))
685
+ return;
686
+ const text = resolvePayloadText(payload);
687
+ await emitFullRuntimeText("reasoning", text, resolveOutboundMediaUrls(payload).filter(Boolean));
688
+ const trimmed = text.trim();
689
+ if (trimmed)
690
+ reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
691
+ }
692
+ : undefined,
693
+ onToolStart: splitFullOutput
694
+ ? async (payload) => {
695
+ if (consumeTerminalSend("tool-start"))
696
+ return;
697
+ await emitFullSegment(formatToolStartSummary(payload));
698
+ }
699
+ : undefined,
700
+ onToolResult: splitFullOutput
701
+ ? async (payload) => {
702
+ if (consumeTerminalSend("tool-result"))
703
+ return;
704
+ const text = resolvePayloadText(payload);
705
+ if (isDefaultToolResultText(text))
706
+ return;
707
+ await emitFullRuntimeText("tool result", text, resolveOutboundMediaUrls(payload).filter(Boolean));
708
+ }
709
+ : undefined,
710
+ onItemEvent: splitFullOutput
711
+ ? async (payload) => {
712
+ if (consumeTerminalSend("item-event"))
713
+ return;
714
+ if (isToolProgressItem(payload))
715
+ return;
716
+ await emitFullRuntimeText("progress", summarizeProgressPayload(payload));
717
+ }
718
+ : undefined,
719
+ onPlanUpdate: splitFullOutput
720
+ ? async (payload) => {
721
+ if (consumeTerminalSend("plan-update"))
722
+ return;
723
+ await emitFullRuntimeText("plan", summarizeProgressPayload(payload));
724
+ }
725
+ : undefined,
726
+ onCommandOutput: splitFullOutput
727
+ ? async (payload) => {
728
+ if (consumeTerminalSend("command-output"))
729
+ return;
730
+ await emitFullSegment(formatCommandOutputSummary(payload));
731
+ }
732
+ : undefined,
733
+ onPatchSummary: splitFullOutput
734
+ ? async (payload) => {
735
+ if (consumeTerminalSend("patch-summary"))
736
+ return;
737
+ await emitFullSegment(formatPatchSummary(payload));
738
+ }
739
+ : undefined,
740
+ onCompactionStart: splitFullOutput
741
+ ? async () => {
742
+ if (consumeTerminalSend("compaction-start"))
743
+ return;
744
+ await emitFullSegment("[compaction] started");
745
+ }
746
+ : undefined,
747
+ onCompactionEnd: splitFullOutput
748
+ ? async () => {
749
+ if (consumeTerminalSend("compaction-end"))
750
+ return;
751
+ await emitFullSegment("[compaction] finished");
752
+ }
753
+ : undefined,
479
754
  },
480
755
  markDispatchIdle: base.markDispatchIdle,
481
756
  };
@@ -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",