@clawling/clawchat-plugin-openclaw 2026.5.12-31 → 2026.5.12-32
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/src/commands.js +4 -6
- package/dist/src/config.js +36 -0
- package/dist/src/reply-dispatcher.js +65 -14
- package/package.json +1 -1
- package/src/commands.ts +4 -6
- package/src/config.ts +44 -0
- package/src/reply-dispatcher.ts +64 -16
package/dist/src/commands.js
CHANGED
|
@@ -64,18 +64,16 @@ function persistOutputVisibility(draft, chatId, outputVisibility) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
function formatOutputVisibilityResult(outputVisibility) {
|
|
67
|
-
const runtimeStatus = outputVisibility === "full" ? "on" : "off";
|
|
68
67
|
const detailLevel = {
|
|
69
|
-
minimal: "
|
|
70
|
-
normal: "
|
|
71
|
-
full: "
|
|
68
|
+
minimal: "final only",
|
|
69
|
+
normal: "final plus block media",
|
|
70
|
+
full: "final plus buffered reasoning, tool/progress, and block output",
|
|
72
71
|
};
|
|
73
72
|
return [
|
|
74
73
|
"**ClawChat output updated**",
|
|
75
74
|
"",
|
|
76
75
|
`- visibility: \`${outputVisibility}\``,
|
|
77
|
-
`-
|
|
78
|
-
`- detail level: \`${detailLevel[outputVisibility]}\``,
|
|
76
|
+
`- output: \`${detailLevel[outputVisibility]}\``,
|
|
79
77
|
"",
|
|
80
78
|
"Applies to new ClawChat messages.",
|
|
81
79
|
].join("\n");
|
package/dist/src/config.js
CHANGED
|
@@ -191,6 +191,29 @@ function readGroupMode(value) {
|
|
|
191
191
|
function readGroupCommandMode(value) {
|
|
192
192
|
return value === "all" || value === "off" ? value : "owner";
|
|
193
193
|
}
|
|
194
|
+
function readOutputVisibility(value) {
|
|
195
|
+
return value === "minimal" || value === "full" ? value : "normal";
|
|
196
|
+
}
|
|
197
|
+
function readOptionalOutputVisibility(value) {
|
|
198
|
+
return value === "minimal" || value === "normal" || value === "full" ? value : undefined;
|
|
199
|
+
}
|
|
200
|
+
function readChats(value) {
|
|
201
|
+
const rawChats = value && typeof value === "object" && !Array.isArray(value)
|
|
202
|
+
? value
|
|
203
|
+
: {};
|
|
204
|
+
const chats = {};
|
|
205
|
+
for (const [chatId, rawChat] of Object.entries(rawChats)) {
|
|
206
|
+
if (!chatId)
|
|
207
|
+
continue;
|
|
208
|
+
const chat = rawChat && typeof rawChat === "object" && !Array.isArray(rawChat)
|
|
209
|
+
? rawChat
|
|
210
|
+
: {};
|
|
211
|
+
const outputVisibility = readOptionalOutputVisibility(chat.outputVisibility);
|
|
212
|
+
if (outputVisibility)
|
|
213
|
+
chats[chatId] = { outputVisibility };
|
|
214
|
+
}
|
|
215
|
+
return chats;
|
|
216
|
+
}
|
|
194
217
|
function readGroups(value) {
|
|
195
218
|
const rawGroups = value && typeof value === "object" && !Array.isArray(value)
|
|
196
219
|
? value
|
|
@@ -202,9 +225,11 @@ function readGroups(value) {
|
|
|
202
225
|
const group = rawGroup && typeof rawGroup === "object" && !Array.isArray(rawGroup)
|
|
203
226
|
? rawGroup
|
|
204
227
|
: {};
|
|
228
|
+
const outputVisibility = readOptionalOutputVisibility(group.outputVisibility);
|
|
205
229
|
groups[chatId] = {
|
|
206
230
|
groupMode: readGroupMode(group.groupMode),
|
|
207
231
|
groupCommandMode: readGroupCommandMode(group.groupCommandMode),
|
|
232
|
+
...(outputVisibility ? { outputVisibility } : {}),
|
|
208
233
|
};
|
|
209
234
|
}
|
|
210
235
|
return groups;
|
|
@@ -219,6 +244,13 @@ export function effectiveGroupCommandMode(account, chatId) {
|
|
|
219
244
|
?? account.groups["*"]?.groupCommandMode
|
|
220
245
|
?? account.groupCommandMode;
|
|
221
246
|
}
|
|
247
|
+
export function effectiveOutputVisibility(account, chatId, chatType) {
|
|
248
|
+
return account.chats?.[chatId]?.outputVisibility
|
|
249
|
+
?? (chatType === "group" ? account.groups?.[chatId]?.outputVisibility : undefined)
|
|
250
|
+
?? (chatType === "group" ? account.groups?.["*"]?.outputVisibility : undefined)
|
|
251
|
+
?? account.outputVisibility
|
|
252
|
+
?? "normal";
|
|
253
|
+
}
|
|
222
254
|
function readReconnect(raw) {
|
|
223
255
|
const s = raw && typeof raw === "object" ? raw : {};
|
|
224
256
|
return {
|
|
@@ -261,6 +293,8 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
261
293
|
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
262
294
|
const ownerUserId = readOptionalString(channel.ownerUserId) || readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV);
|
|
263
295
|
const enabled = typeof channel.enabled === "boolean" ? channel.enabled : true;
|
|
296
|
+
const outputVisibility = readOutputVisibility(channel.outputVisibility);
|
|
297
|
+
const chats = readChats(channel.chats);
|
|
264
298
|
const groupMode = readGroupMode(channel.groupMode);
|
|
265
299
|
const groupCommandMode = readGroupCommandMode(channel.groupCommandMode);
|
|
266
300
|
const groups = readGroups(channel.groups);
|
|
@@ -284,6 +318,8 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
284
318
|
agentId,
|
|
285
319
|
userId,
|
|
286
320
|
ownerUserId,
|
|
321
|
+
outputVisibility,
|
|
322
|
+
chats,
|
|
287
323
|
groupMode,
|
|
288
324
|
groupCommandMode,
|
|
289
325
|
groups,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { interactiveReplyToPresentation, renderMessagePresentationFallbackText, } from "openclaw/plugin-sdk/interactive-runtime";
|
|
2
2
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
3
3
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
4
|
+
import { effectiveOutputVisibility, } from "./config.js";
|
|
4
5
|
import { uploadOutboundMedia } from "./media-runtime.js";
|
|
5
6
|
import { sendOpenclawClawlingText, } from "./outbound.js";
|
|
6
7
|
import { isClawChatNoopResponseText } from "./profile-prompt.js";
|
|
@@ -106,15 +107,14 @@ function resolvePayloadText(payload) {
|
|
|
106
107
|
/**
|
|
107
108
|
* Reply dispatcher for clawchat-plugin-openclaw.
|
|
108
109
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* blocks, the dispatcher buffers or ignores them and only emits materialized
|
|
113
|
-
* `message.send` / `message.reply` frames for the final reply.
|
|
110
|
+
* 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.
|
|
114
113
|
*/
|
|
115
114
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
116
115
|
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, log, } = options;
|
|
117
116
|
const isGroupTarget = target.chatType === "group";
|
|
117
|
+
const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
|
|
118
118
|
const ownerDirectTarget = () => {
|
|
119
119
|
const ownerUserId = account.ownerUserId?.trim();
|
|
120
120
|
return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
|
|
@@ -142,6 +142,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
142
142
|
}
|
|
143
143
|
// ----- Reply state ------------------------------------------------------
|
|
144
144
|
let reasoningText = "";
|
|
145
|
+
let bufferedOutputText = "";
|
|
146
|
+
const bufferedOutputUrls = [];
|
|
145
147
|
let runDone = false;
|
|
146
148
|
let typingActive = false;
|
|
147
149
|
let terminalReplySuppressed = false;
|
|
@@ -229,6 +231,37 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
229
231
|
recordOutbound("thinking", messageId, thinkingText);
|
|
230
232
|
reasoningText = "";
|
|
231
233
|
};
|
|
234
|
+
const resetBufferedOutput = () => {
|
|
235
|
+
bufferedOutputText = "";
|
|
236
|
+
bufferedOutputUrls.length = 0;
|
|
237
|
+
};
|
|
238
|
+
const appendBufferedText = (value) => {
|
|
239
|
+
const trimmed = value.trim();
|
|
240
|
+
if (!trimmed)
|
|
241
|
+
return;
|
|
242
|
+
bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
|
|
243
|
+
};
|
|
244
|
+
const appendBufferedUrls = (urls) => {
|
|
245
|
+
for (const url of urls) {
|
|
246
|
+
if (url && !bufferedOutputUrls.includes(url))
|
|
247
|
+
bufferedOutputUrls.push(url);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const mergeFinalText = (text) => {
|
|
251
|
+
if (outputVisibility !== "full")
|
|
252
|
+
return text;
|
|
253
|
+
return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
|
|
254
|
+
};
|
|
255
|
+
const mergeFinalUrls = (urls) => {
|
|
256
|
+
if (outputVisibility === "minimal")
|
|
257
|
+
return urls;
|
|
258
|
+
const merged = bufferedOutputUrls.slice();
|
|
259
|
+
for (const url of urls) {
|
|
260
|
+
if (url && !merged.includes(url))
|
|
261
|
+
merged.push(url);
|
|
262
|
+
}
|
|
263
|
+
return merged;
|
|
264
|
+
};
|
|
232
265
|
const mintStaticMessageId = () => `${account.userId}-msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
233
266
|
const emitTyping = (isTyping) => {
|
|
234
267
|
if (!isTyping && !typingActive)
|
|
@@ -342,6 +375,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
342
375
|
onReplyStart: async () => {
|
|
343
376
|
emitTyping(true);
|
|
344
377
|
reasoningText = "";
|
|
378
|
+
resetBufferedOutput();
|
|
345
379
|
runDone = false;
|
|
346
380
|
},
|
|
347
381
|
deliver: async (payload, info) => {
|
|
@@ -362,13 +396,30 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
362
396
|
return;
|
|
363
397
|
}
|
|
364
398
|
if (payload.isReasoning) {
|
|
365
|
-
if (isGroupTarget ||
|
|
399
|
+
if (isGroupTarget || outputVisibility !== "full")
|
|
366
400
|
return;
|
|
367
|
-
|
|
401
|
+
appendBufferedText(text);
|
|
402
|
+
const trimmed = text.trim();
|
|
403
|
+
if (trimmed)
|
|
404
|
+
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (info?.kind === "tool") {
|
|
408
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
409
|
+
appendBufferedText(text);
|
|
410
|
+
appendBufferedUrls(urls);
|
|
411
|
+
}
|
|
368
412
|
return;
|
|
369
413
|
}
|
|
370
|
-
if (info?.kind === "
|
|
414
|
+
if (info?.kind === "block") {
|
|
415
|
+
if (!isGroupTarget && outputVisibility === "normal")
|
|
416
|
+
appendBufferedUrls(urls);
|
|
417
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
418
|
+
appendBufferedText(text);
|
|
419
|
+
appendBufferedUrls(urls);
|
|
420
|
+
}
|
|
371
421
|
return;
|
|
422
|
+
}
|
|
372
423
|
if (info?.kind === "final") {
|
|
373
424
|
if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
|
|
374
425
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
|
|
@@ -391,15 +442,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
391
442
|
});
|
|
392
443
|
return;
|
|
393
444
|
}
|
|
394
|
-
const
|
|
395
|
-
const
|
|
445
|
+
const finalText = richFragment && account.richInteractions ? mergeFinalText("") : mergeFinalText(text);
|
|
446
|
+
const finalUrls = mergeFinalUrls(urls);
|
|
447
|
+
const mediaFragments = await uploadMediaUrls(finalUrls);
|
|
448
|
+
const result = await sendStatic(finalText, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
|
|
396
449
|
if (result?.messageId)
|
|
397
450
|
recordThinkingIfLinked(result.messageId);
|
|
398
451
|
return;
|
|
399
452
|
}
|
|
400
|
-
//
|
|
401
|
-
// the model is producing output. ClawChat gets only the final materialized
|
|
402
|
-
// reply.
|
|
453
|
+
// Unknown delivery kind: keep ClawChat output tied to OpenClaw final.
|
|
403
454
|
},
|
|
404
455
|
onError: (error, info) => {
|
|
405
456
|
const errorText = normalizeReplyErrorText(error);
|
|
@@ -424,7 +475,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
424
475
|
replyOptions: {
|
|
425
476
|
...base.replyOptions,
|
|
426
477
|
sourceReplyDeliveryMode: "automatic",
|
|
427
|
-
disableBlockStreaming:
|
|
478
|
+
disableBlockStreaming: outputVisibility !== "full",
|
|
428
479
|
},
|
|
429
480
|
markDispatchIdle: base.markDispatchIdle,
|
|
430
481
|
};
|
package/package.json
CHANGED
package/src/commands.ts
CHANGED
|
@@ -76,18 +76,16 @@ function persistOutputVisibility(
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function formatOutputVisibilityResult(outputVisibility: OutputVisibility): string {
|
|
79
|
-
const runtimeStatus = outputVisibility === "full" ? "on" : "off";
|
|
80
79
|
const detailLevel: Record<OutputVisibility, string> = {
|
|
81
|
-
minimal: "
|
|
82
|
-
normal: "
|
|
83
|
-
full: "
|
|
80
|
+
minimal: "final only",
|
|
81
|
+
normal: "final plus block media",
|
|
82
|
+
full: "final plus buffered reasoning, tool/progress, and block output",
|
|
84
83
|
};
|
|
85
84
|
return [
|
|
86
85
|
"**ClawChat output updated**",
|
|
87
86
|
"",
|
|
88
87
|
`- visibility: \`${outputVisibility}\``,
|
|
89
|
-
`-
|
|
90
|
-
`- detail level: \`${detailLevel[outputVisibility]}\``,
|
|
88
|
+
`- output: \`${detailLevel[outputVisibility]}\``,
|
|
91
89
|
"",
|
|
92
90
|
"Applies to new ClawChat messages.",
|
|
93
91
|
].join("\n");
|
package/src/config.ts
CHANGED
|
@@ -266,6 +266,8 @@ export type ResolvedOpenclawClawlingAccount = {
|
|
|
266
266
|
agentId: string;
|
|
267
267
|
userId: string;
|
|
268
268
|
ownerUserId: string;
|
|
269
|
+
outputVisibility: OutputVisibility;
|
|
270
|
+
chats: Record<string, OpenclawClawlingChatConfig>;
|
|
269
271
|
groupMode: GroupMode;
|
|
270
272
|
groupCommandMode: GroupCommandMode;
|
|
271
273
|
groups: Record<string, OpenclawClawlingGroupConfig>;
|
|
@@ -315,6 +317,30 @@ function readGroupCommandMode(value: unknown): GroupCommandMode {
|
|
|
315
317
|
return value === "all" || value === "off" ? value : "owner";
|
|
316
318
|
}
|
|
317
319
|
|
|
320
|
+
function readOutputVisibility(value: unknown): OutputVisibility {
|
|
321
|
+
return value === "minimal" || value === "full" ? value : "normal";
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function readOptionalOutputVisibility(value: unknown): OutputVisibility | undefined {
|
|
325
|
+
return value === "minimal" || value === "normal" || value === "full" ? value : undefined;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function readChats(value: unknown): Record<string, OpenclawClawlingChatConfig> {
|
|
329
|
+
const rawChats = value && typeof value === "object" && !Array.isArray(value)
|
|
330
|
+
? (value as Record<string, unknown>)
|
|
331
|
+
: {};
|
|
332
|
+
const chats: Record<string, OpenclawClawlingChatConfig> = {};
|
|
333
|
+
for (const [chatId, rawChat] of Object.entries(rawChats)) {
|
|
334
|
+
if (!chatId) continue;
|
|
335
|
+
const chat = rawChat && typeof rawChat === "object" && !Array.isArray(rawChat)
|
|
336
|
+
? (rawChat as Record<string, unknown>)
|
|
337
|
+
: {};
|
|
338
|
+
const outputVisibility = readOptionalOutputVisibility(chat.outputVisibility);
|
|
339
|
+
if (outputVisibility) chats[chatId] = { outputVisibility };
|
|
340
|
+
}
|
|
341
|
+
return chats;
|
|
342
|
+
}
|
|
343
|
+
|
|
318
344
|
function readGroups(value: unknown): Record<string, OpenclawClawlingGroupConfig> {
|
|
319
345
|
const rawGroups = value && typeof value === "object" && !Array.isArray(value)
|
|
320
346
|
? (value as Record<string, unknown>)
|
|
@@ -325,9 +351,11 @@ function readGroups(value: unknown): Record<string, OpenclawClawlingGroupConfig>
|
|
|
325
351
|
const group = rawGroup && typeof rawGroup === "object" && !Array.isArray(rawGroup)
|
|
326
352
|
? (rawGroup as Record<string, unknown>)
|
|
327
353
|
: {};
|
|
354
|
+
const outputVisibility = readOptionalOutputVisibility(group.outputVisibility);
|
|
328
355
|
groups[chatId] = {
|
|
329
356
|
groupMode: readGroupMode(group.groupMode),
|
|
330
357
|
groupCommandMode: readGroupCommandMode(group.groupCommandMode),
|
|
358
|
+
...(outputVisibility ? { outputVisibility } : {}),
|
|
331
359
|
};
|
|
332
360
|
}
|
|
333
361
|
return groups;
|
|
@@ -351,6 +379,18 @@ export function effectiveGroupCommandMode(
|
|
|
351
379
|
?? account.groupCommandMode;
|
|
352
380
|
}
|
|
353
381
|
|
|
382
|
+
export function effectiveOutputVisibility(
|
|
383
|
+
account: Pick<ResolvedOpenclawClawlingAccount, "outputVisibility" | "chats" | "groups">,
|
|
384
|
+
chatId: string,
|
|
385
|
+
chatType: "direct" | "group",
|
|
386
|
+
): OutputVisibility {
|
|
387
|
+
return account.chats?.[chatId]?.outputVisibility
|
|
388
|
+
?? (chatType === "group" ? account.groups?.[chatId]?.outputVisibility : undefined)
|
|
389
|
+
?? (chatType === "group" ? account.groups?.["*"]?.outputVisibility : undefined)
|
|
390
|
+
?? account.outputVisibility
|
|
391
|
+
?? "normal";
|
|
392
|
+
}
|
|
393
|
+
|
|
354
394
|
function readReconnect(raw: unknown): Required<OpenclawClawlingReconnectConfig> {
|
|
355
395
|
const s = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {};
|
|
356
396
|
return {
|
|
@@ -406,6 +446,8 @@ export function resolveOpenclawClawlingAccount(
|
|
|
406
446
|
const ownerUserId =
|
|
407
447
|
readOptionalString(channel.ownerUserId) || readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV);
|
|
408
448
|
const enabled = typeof channel.enabled === "boolean" ? channel.enabled : true;
|
|
449
|
+
const outputVisibility = readOutputVisibility(channel.outputVisibility);
|
|
450
|
+
const chats = readChats(channel.chats);
|
|
409
451
|
const groupMode = readGroupMode(channel.groupMode);
|
|
410
452
|
const groupCommandMode = readGroupCommandMode(channel.groupCommandMode);
|
|
411
453
|
const groups = readGroups(channel.groups);
|
|
@@ -433,6 +475,8 @@ export function resolveOpenclawClawlingAccount(
|
|
|
433
475
|
agentId,
|
|
434
476
|
userId,
|
|
435
477
|
ownerUserId,
|
|
478
|
+
outputVisibility,
|
|
479
|
+
chats,
|
|
436
480
|
groupMode,
|
|
437
481
|
groupCommandMode,
|
|
438
482
|
groups,
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -13,7 +13,10 @@ import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
|
13
13
|
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
|
14
14
|
import { createOpenclawClawlingApiClient } from "./api-client.ts";
|
|
15
15
|
import type { ChatType } from "./client.ts";
|
|
16
|
-
import
|
|
16
|
+
import {
|
|
17
|
+
effectiveOutputVisibility,
|
|
18
|
+
type ResolvedOpenclawClawlingAccount,
|
|
19
|
+
} from "./config.ts";
|
|
17
20
|
import { uploadOutboundMedia, type ClawlingMediaFragment } from "./media-runtime.ts";
|
|
18
21
|
import {
|
|
19
22
|
sendOpenclawClawlingText,
|
|
@@ -65,7 +68,7 @@ type TypedReplyDispatcherResult = ReturnType<
|
|
|
65
68
|
type ClawChatReplyOptions = TypedReplyDispatcherResult["replyOptions"] &
|
|
66
69
|
{
|
|
67
70
|
sourceReplyDeliveryMode: "automatic";
|
|
68
|
-
disableBlockStreaming:
|
|
71
|
+
disableBlockStreaming: boolean;
|
|
69
72
|
};
|
|
70
73
|
|
|
71
74
|
type RichAction = {
|
|
@@ -188,11 +191,9 @@ function resolvePayloadText(payload: ReplyPayload): string {
|
|
|
188
191
|
/**
|
|
189
192
|
* Reply dispatcher for clawchat-plugin-openclaw.
|
|
190
193
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* blocks, the dispatcher buffers or ignores them and only emits materialized
|
|
195
|
-
* `message.send` / `message.reply` frames for the final reply.
|
|
194
|
+
* ClawChat emits only materialized `message.send` / `message.reply` frames for
|
|
195
|
+
* the final reply. Non-final OpenClaw deliveries are ignored or buffered
|
|
196
|
+
* according to outputVisibility and are never sent as separate ClawChat frames.
|
|
196
197
|
*/
|
|
197
198
|
export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOptions): {
|
|
198
199
|
dispatcher: TypedReplyDispatcherResult["dispatcher"];
|
|
@@ -211,6 +212,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
211
212
|
log,
|
|
212
213
|
} = options;
|
|
213
214
|
const isGroupTarget = target.chatType === "group";
|
|
215
|
+
const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
|
|
214
216
|
const ownerDirectTarget = () => {
|
|
215
217
|
const ownerUserId = account.ownerUserId?.trim();
|
|
216
218
|
return ownerUserId ? { chatId: ownerUserId, chatType: "direct" as const } : null;
|
|
@@ -242,6 +244,8 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
242
244
|
// ----- Reply state ------------------------------------------------------
|
|
243
245
|
|
|
244
246
|
let reasoningText = "";
|
|
247
|
+
let bufferedOutputText = "";
|
|
248
|
+
const bufferedOutputUrls: string[] = [];
|
|
245
249
|
let runDone = false;
|
|
246
250
|
let typingActive = false;
|
|
247
251
|
let terminalReplySuppressed = false;
|
|
@@ -327,6 +331,32 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
327
331
|
recordOutbound("thinking", messageId, thinkingText);
|
|
328
332
|
reasoningText = "";
|
|
329
333
|
};
|
|
334
|
+
const resetBufferedOutput = () => {
|
|
335
|
+
bufferedOutputText = "";
|
|
336
|
+
bufferedOutputUrls.length = 0;
|
|
337
|
+
};
|
|
338
|
+
const appendBufferedText = (value: string) => {
|
|
339
|
+
const trimmed = value.trim();
|
|
340
|
+
if (!trimmed) return;
|
|
341
|
+
bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
|
|
342
|
+
};
|
|
343
|
+
const appendBufferedUrls = (urls: string[]) => {
|
|
344
|
+
for (const url of urls) {
|
|
345
|
+
if (url && !bufferedOutputUrls.includes(url)) bufferedOutputUrls.push(url);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const mergeFinalText = (text: string): string => {
|
|
349
|
+
if (outputVisibility !== "full") return text;
|
|
350
|
+
return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
|
|
351
|
+
};
|
|
352
|
+
const mergeFinalUrls = (urls: string[]): string[] => {
|
|
353
|
+
if (outputVisibility === "minimal") return urls;
|
|
354
|
+
const merged = bufferedOutputUrls.slice();
|
|
355
|
+
for (const url of urls) {
|
|
356
|
+
if (url && !merged.includes(url)) merged.push(url);
|
|
357
|
+
}
|
|
358
|
+
return merged;
|
|
359
|
+
};
|
|
330
360
|
|
|
331
361
|
const mintStaticMessageId = () =>
|
|
332
362
|
`${account.userId}-msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -462,6 +492,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
462
492
|
onReplyStart: async () => {
|
|
463
493
|
emitTyping(true);
|
|
464
494
|
reasoningText = "";
|
|
495
|
+
resetBufferedOutput();
|
|
465
496
|
runDone = false;
|
|
466
497
|
},
|
|
467
498
|
deliver: async (payload: ReplyPayload, info?: { kind: "tool" | "block" | "final" }) => {
|
|
@@ -488,12 +519,29 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
488
519
|
}
|
|
489
520
|
|
|
490
521
|
if (payload.isReasoning) {
|
|
491
|
-
if (isGroupTarget ||
|
|
492
|
-
|
|
522
|
+
if (isGroupTarget || outputVisibility !== "full") return;
|
|
523
|
+
appendBufferedText(text);
|
|
524
|
+
const trimmed = text.trim();
|
|
525
|
+
if (trimmed) reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
493
526
|
return;
|
|
494
527
|
}
|
|
495
528
|
|
|
496
|
-
if (info?.kind === "tool")
|
|
529
|
+
if (info?.kind === "tool") {
|
|
530
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
531
|
+
appendBufferedText(text);
|
|
532
|
+
appendBufferedUrls(urls);
|
|
533
|
+
}
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (info?.kind === "block") {
|
|
538
|
+
if (!isGroupTarget && outputVisibility === "normal") appendBufferedUrls(urls);
|
|
539
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
540
|
+
appendBufferedText(text);
|
|
541
|
+
appendBufferedUrls(urls);
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
497
545
|
|
|
498
546
|
if (info?.kind === "final") {
|
|
499
547
|
if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
|
|
@@ -517,9 +565,11 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
517
565
|
});
|
|
518
566
|
return;
|
|
519
567
|
}
|
|
520
|
-
const
|
|
568
|
+
const finalText = richFragment && account.richInteractions ? mergeFinalText("") : mergeFinalText(text);
|
|
569
|
+
const finalUrls = mergeFinalUrls(urls);
|
|
570
|
+
const mediaFragments = await uploadMediaUrls(finalUrls);
|
|
521
571
|
const result = await sendStatic(
|
|
522
|
-
|
|
572
|
+
finalText,
|
|
523
573
|
mediaFragments,
|
|
524
574
|
richFragment && account.richInteractions ? ([richFragment] as unknown as Fragment[]) : [],
|
|
525
575
|
{ recordMessage: true },
|
|
@@ -528,9 +578,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
528
578
|
return;
|
|
529
579
|
}
|
|
530
580
|
|
|
531
|
-
//
|
|
532
|
-
// the model is producing output. ClawChat gets only the final materialized
|
|
533
|
-
// reply.
|
|
581
|
+
// Unknown delivery kind: keep ClawChat output tied to OpenClaw final.
|
|
534
582
|
},
|
|
535
583
|
onError: (error: unknown, info: { kind: string }) => {
|
|
536
584
|
const errorText = normalizeReplyErrorText(error);
|
|
@@ -559,7 +607,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
559
607
|
replyOptions: {
|
|
560
608
|
...base.replyOptions,
|
|
561
609
|
sourceReplyDeliveryMode: "automatic",
|
|
562
|
-
disableBlockStreaming:
|
|
610
|
+
disableBlockStreaming: outputVisibility !== "full",
|
|
563
611
|
},
|
|
564
612
|
markDispatchIdle: base.markDispatchIdle,
|
|
565
613
|
};
|