@newbase-clawchat/openclaw-clawchat 2026.5.4 → 2026.5.12-13
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/INSTALL.md +64 -0
- package/README.md +121 -19
- package/dist/index.js +10 -19
- package/dist/setup-entry.js +3 -0
- package/dist/src/api-client.js +78 -10
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/channel.js +25 -156
- package/dist/src/channel.setup.js +120 -0
- package/dist/src/client.js +37 -41
- package/dist/src/config.js +75 -17
- package/dist/src/inbound.js +79 -61
- package/dist/src/login.runtime.js +84 -19
- package/dist/src/media-runtime.js +8 -8
- package/dist/src/message-mapper.js +1 -1
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +410 -26
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +2 -7
- package/dist/src/reply-dispatcher.js +157 -54
- package/dist/src/runtime.js +795 -119
- package/dist/src/storage.js +689 -0
- package/dist/src/tools-schema.js +98 -16
- package/dist/src/tools.js +422 -135
- package/dist/src/ws-alignment.js +178 -0
- package/dist/src/ws-client.js +588 -0
- package/dist/src/ws-log.js +19 -0
- package/index.ts +10 -22
- package/openclaw.plugin.json +37 -2
- package/package.json +17 -4
- package/setup-entry.ts +4 -0
- package/skills/clawchat/SKILL.md +88 -0
- package/src/api-client.test.ts +274 -14
- package/src/api-client.ts +138 -23
- package/src/api-types.test-d.ts +12 -0
- package/src/api-types.ts +90 -4
- package/src/buffered-stream.test.ts +14 -12
- package/src/buffered-stream.ts +1 -1
- package/src/channel.outbound.test.ts +269 -60
- package/src/channel.setup.ts +146 -0
- package/src/channel.test.ts +130 -24
- package/src/channel.ts +30 -186
- package/src/client.test.ts +197 -11
- package/src/client.ts +50 -57
- package/src/config.test.ts +108 -6
- package/src/config.ts +95 -24
- package/src/inbound.test.ts +288 -37
- package/src/inbound.ts +96 -84
- package/src/login.runtime.test.ts +347 -13
- package/src/login.runtime.ts +105 -23
- package/src/manifest.test.ts +146 -74
- package/src/media-runtime.test.ts +57 -2
- package/src/media-runtime.ts +26 -17
- package/src/message-mapper.test.ts +2 -2
- package/src/message-mapper.ts +2 -2
- package/src/mock-transport.test.ts +35 -0
- package/src/mock-transport.ts +38 -0
- package/src/outbound.test.ts +694 -73
- package/src/outbound.ts +484 -31
- package/src/plugin-entry.test.ts +1 -0
- package/src/protocol-types.test.ts +69 -0
- package/src/protocol-types.ts +296 -0
- package/src/protocol-types.typecheck.ts +89 -0
- package/src/protocol.test.ts +1 -6
- package/src/protocol.ts +2 -7
- package/src/reply-dispatcher.test.ts +819 -119
- package/src/reply-dispatcher.ts +202 -60
- package/src/runtime.test.ts +2120 -41
- package/src/runtime.ts +935 -142
- package/src/scripts.test.ts +85 -0
- package/src/storage.test.ts +793 -0
- package/src/storage.ts +1095 -0
- package/src/streaming.test.ts +9 -8
- package/src/streaming.ts +1 -1
- package/src/tools-schema.ts +148 -20
- package/src/tools.test.ts +377 -50
- package/src/tools.ts +574 -154
- package/src/ws-alignment.test.ts +103 -0
- package/src/ws-alignment.ts +275 -0
- package/src/ws-client.test.ts +1218 -0
- package/src/ws-client.ts +662 -0
- package/src/ws-log.test.ts +32 -0
- package/src/ws-log.ts +31 -0
- package/skills/clawchat-account-tools/SKILL.md +0 -26
- package/skills/clawchat-activate/SKILL.md +0 -47
package/src/reply-dispatcher.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Fragment } from "./protocol-types.ts";
|
|
2
|
+
import type { ClawlingChatClient } from "./ws-client.ts";
|
|
2
3
|
import {
|
|
3
4
|
interactiveReplyToPresentation,
|
|
4
5
|
renderMessagePresentationFallbackText,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
} from "openclaw/plugin-sdk/interactive-runtime";
|
|
9
10
|
import type { PluginRuntime } from "openclaw/plugin-sdk/core";
|
|
10
11
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
12
|
+
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
11
13
|
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
|
12
14
|
import { createOpenclawClawlingApiClient } from "./api-client.ts";
|
|
13
15
|
import {
|
|
@@ -15,12 +17,16 @@ import {
|
|
|
15
17
|
mergeStreamingText,
|
|
16
18
|
type BufferedStreamSession,
|
|
17
19
|
} from "./buffered-stream.ts";
|
|
18
|
-
import {
|
|
20
|
+
import type { ChatType, EnvelopeRouting } from "./client.ts";
|
|
19
21
|
import type { ResolvedOpenclawClawlingAccount } from "./config.ts";
|
|
20
|
-
import { textToFragments } from "./message-mapper.ts";
|
|
21
22
|
import { uploadOutboundMedia, type ClawlingMediaFragment } from "./media-runtime.ts";
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
sendOpenclawClawlingText,
|
|
25
|
+
type OutboundReplyCtx,
|
|
26
|
+
type SendResult,
|
|
27
|
+
} from "./outbound.ts";
|
|
23
28
|
import { sendStreamingFailure } from "./streaming.ts";
|
|
29
|
+
import type { ClawChatStore } from "./storage.ts";
|
|
24
30
|
|
|
25
31
|
export interface ReplyDispatcherOptions {
|
|
26
32
|
cfg: OpenClawConfig;
|
|
@@ -51,6 +57,7 @@ export interface ReplyDispatcherOptions {
|
|
|
51
57
|
senderNickName: string;
|
|
52
58
|
bodyText: string;
|
|
53
59
|
};
|
|
60
|
+
store?: Pick<ClawChatStore, "insertMessage" | "claimMessageOnce" | "updateMessageByIdentity"> | null;
|
|
54
61
|
log?: { info?: (m: string) => void; error?: (m: string) => void };
|
|
55
62
|
}
|
|
56
63
|
|
|
@@ -63,6 +70,11 @@ type StreamingReplyHooks = {
|
|
|
63
70
|
onReasoningStream?: (payload: ReplyPayload) => Promise<void>;
|
|
64
71
|
};
|
|
65
72
|
|
|
73
|
+
type ClawChatReplyOptions = TypedReplyDispatcherResult["replyOptions"] &
|
|
74
|
+
StreamingReplyHooks & {
|
|
75
|
+
sourceReplyDeliveryMode: "automatic";
|
|
76
|
+
};
|
|
77
|
+
|
|
66
78
|
type RichAction = {
|
|
67
79
|
id: string;
|
|
68
80
|
label: string;
|
|
@@ -79,6 +91,8 @@ type RichInteractionFragment = {
|
|
|
79
91
|
actions: RichAction[];
|
|
80
92
|
};
|
|
81
93
|
|
|
94
|
+
const CLIENT_SAFE_REPLY_FAILURE_TEXT = "OpenClaw could not complete this reply.";
|
|
95
|
+
|
|
82
96
|
function normalizeReplyErrorText(error: unknown): string {
|
|
83
97
|
const raw = String(error);
|
|
84
98
|
const retryWrapped = raw.match(/^Error: Retry failed for delivery [^:]+:\s*(.+)$/s);
|
|
@@ -192,7 +206,7 @@ function resolvePayloadText(payload: ReplyPayload): string {
|
|
|
192
206
|
*/
|
|
193
207
|
export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOptions): {
|
|
194
208
|
dispatcher: TypedReplyDispatcherResult["dispatcher"];
|
|
195
|
-
replyOptions:
|
|
209
|
+
replyOptions: ClawChatReplyOptions;
|
|
196
210
|
markDispatchIdle: TypedReplyDispatcherResult["markDispatchIdle"];
|
|
197
211
|
} {
|
|
198
212
|
const {
|
|
@@ -204,6 +218,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
204
218
|
replyCtx,
|
|
205
219
|
inboundMessageId,
|
|
206
220
|
inboundForFinalReply,
|
|
221
|
+
store,
|
|
207
222
|
log,
|
|
208
223
|
} = options;
|
|
209
224
|
const routing: EnvelopeRouting = { chatId: target.chatId, chatType: target.chatType };
|
|
@@ -243,18 +258,94 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
243
258
|
let streamingClosed = false;
|
|
244
259
|
let runFailed = false;
|
|
245
260
|
let runDone = false;
|
|
261
|
+
let streamClaimAttempted = false;
|
|
246
262
|
// `streamCreatedEmitted` is the authoritative guard: once a `message.created`
|
|
247
263
|
// has been emitted for this dispatcher instance, never emit another — even
|
|
248
264
|
// if `onReplyStart` fires again or a pre-onReplyStart `onPartialReply`
|
|
249
265
|
// raced the lazy open path.
|
|
250
266
|
let streamCreatedEmitted = false;
|
|
251
267
|
|
|
268
|
+
const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
|
|
269
|
+
const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
|
|
270
|
+
const claimOutbound = (
|
|
271
|
+
eventType: "message.created" | "message.send" | "message.reply",
|
|
272
|
+
messageId: string,
|
|
273
|
+
text: string,
|
|
274
|
+
raw: unknown,
|
|
275
|
+
): true | false | null => {
|
|
276
|
+
if (!store || !messageId) return null;
|
|
277
|
+
try {
|
|
278
|
+
return store.claimMessageOnce({
|
|
279
|
+
platform: "openclaw",
|
|
280
|
+
accountId: account.accountId,
|
|
281
|
+
kind: "message",
|
|
282
|
+
direction: "outbound",
|
|
283
|
+
eventType,
|
|
284
|
+
chatId: target.chatId,
|
|
285
|
+
messageId,
|
|
286
|
+
text,
|
|
287
|
+
raw,
|
|
288
|
+
});
|
|
289
|
+
} catch {
|
|
290
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound claim failed`);
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const updateOutbound = (
|
|
295
|
+
eventType: "message.reply",
|
|
296
|
+
messageId: string,
|
|
297
|
+
text: string,
|
|
298
|
+
raw: unknown,
|
|
299
|
+
) => {
|
|
300
|
+
if (!store || !messageId) return;
|
|
301
|
+
try {
|
|
302
|
+
store.updateMessageByIdentity({
|
|
303
|
+
accountId: account.accountId,
|
|
304
|
+
kind: "message",
|
|
305
|
+
direction: "outbound",
|
|
306
|
+
eventType,
|
|
307
|
+
chatId: target.chatId,
|
|
308
|
+
messageId,
|
|
309
|
+
text,
|
|
310
|
+
raw,
|
|
311
|
+
});
|
|
312
|
+
} catch {
|
|
313
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound update failed; continuing`);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const recordOutbound = (kind: "message" | "thinking", messageId: string, text: string) => {
|
|
317
|
+
if (!store || !messageId) return;
|
|
318
|
+
try {
|
|
319
|
+
store.insertMessage({
|
|
320
|
+
platform: "openclaw",
|
|
321
|
+
accountId: account.accountId,
|
|
322
|
+
kind,
|
|
323
|
+
direction: "outbound",
|
|
324
|
+
eventType: outboundEventType(),
|
|
325
|
+
chatId: target.chatId,
|
|
326
|
+
messageId,
|
|
327
|
+
text,
|
|
328
|
+
raw: outboundRaw(),
|
|
329
|
+
});
|
|
330
|
+
} catch {
|
|
331
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound insert failed; continuing`);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const recordThinkingIfLinked = (messageId: string) => {
|
|
335
|
+
const thinkingText = reasoningText.trim();
|
|
336
|
+
if (!thinkingText) return;
|
|
337
|
+
recordOutbound("thinking", messageId, thinkingText);
|
|
338
|
+
reasoningText = "";
|
|
339
|
+
};
|
|
340
|
+
|
|
252
341
|
const mintStreamingMessageId = () =>
|
|
253
342
|
`${account.userId}-stream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
343
|
+
const mintStaticMessageId = () =>
|
|
344
|
+
`${account.userId}-msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
254
345
|
|
|
255
346
|
const openSessionIfNeeded = () => {
|
|
256
|
-
if (!streamingEnabled || streamingSession || streamCreatedEmitted) return;
|
|
257
|
-
|
|
347
|
+
if (!streamingEnabled || streamingSession || streamCreatedEmitted || streamClaimAttempted) return;
|
|
348
|
+
streamClaimAttempted = true;
|
|
258
349
|
// Mint a fresh agent-side message_id at `message.created` time. All
|
|
259
350
|
// subsequent `message.add` / `message.done` / `message.reply` frames for
|
|
260
351
|
// this stream reuse it. Once the stream finalizes (done or reply), this
|
|
@@ -263,12 +354,27 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
263
354
|
// `replyTo.msgId`; keeping the two distinct avoids the agent's reply
|
|
264
355
|
// frames shadowing the user turn they answer.
|
|
265
356
|
streamingMessageId = mintStreamingMessageId();
|
|
357
|
+
const claimed = claimOutbound(
|
|
358
|
+
"message.created",
|
|
359
|
+
streamingMessageId,
|
|
360
|
+
"",
|
|
361
|
+
{ target, replyCtx: replyCtx ?? null, mode: "stream" },
|
|
362
|
+
);
|
|
363
|
+
if (claimed !== true) {
|
|
364
|
+
streamCreatedEmitted = false;
|
|
365
|
+
streamingMessageId = "";
|
|
366
|
+
log?.[claimed === false ? "info" : "error"]?.(
|
|
367
|
+
`[${account.accountId}] openclaw-clawchat stream outbound skipped reason=${claimed === false ? "duplicate" : "claim_unavailable"}`,
|
|
368
|
+
);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
streamCreatedEmitted = true;
|
|
266
372
|
streamingSession = openBufferedStreamingSession({
|
|
267
373
|
client,
|
|
268
374
|
routing,
|
|
269
375
|
sender: {
|
|
270
376
|
id: account.userId,
|
|
271
|
-
type:
|
|
377
|
+
type: "direct",
|
|
272
378
|
nick_name: account.userId,
|
|
273
379
|
},
|
|
274
380
|
messageId: streamingMessageId,
|
|
@@ -315,16 +421,31 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
315
421
|
text: string,
|
|
316
422
|
mediaFragments: ClawlingMediaFragment[] = [],
|
|
317
423
|
richFragments: Fragment[] = [],
|
|
318
|
-
|
|
319
|
-
|
|
424
|
+
options: { recordMessage?: boolean } = {},
|
|
425
|
+
): Promise<SendResult | null> => {
|
|
426
|
+
if (!text.trim() && mediaFragments.length === 0 && richFragments.length === 0) return null;
|
|
320
427
|
log?.info?.(
|
|
321
428
|
`[${account.accountId}] openclaw-clawchat sending static text_len=${text.length} media=${mediaFragments.length} rich=${richFragments.length} to=${target.chatId}`,
|
|
322
429
|
);
|
|
323
|
-
|
|
430
|
+
const messageId = mintStaticMessageId();
|
|
431
|
+
const raw = { target, replyCtx: replyCtx ?? null, mode: "static" };
|
|
432
|
+
const claimed = options.recordMessage
|
|
433
|
+
? claimOutbound(outboundEventType(), messageId, text, raw)
|
|
434
|
+
: true;
|
|
435
|
+
if (claimed === false) {
|
|
436
|
+
log?.info?.(`[${account.accountId}] openclaw-clawchat outbound duplicate skipped msg=${messageId}`);
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
if (claimed === null) {
|
|
440
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat outbound skipped msg=${messageId} reason=claim_unavailable`);
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const result = await sendOpenclawClawlingText({
|
|
324
444
|
client,
|
|
325
445
|
account,
|
|
326
446
|
to: target,
|
|
327
447
|
text,
|
|
448
|
+
messageId,
|
|
328
449
|
...(replyCtx ? { replyCtx } : {}),
|
|
329
450
|
...(richFragments.length > 0 ? { richFragments } : {}),
|
|
330
451
|
...(mediaFragments.length > 0 ? { mediaFragments } : {}),
|
|
@@ -333,11 +454,23 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
333
454
|
log?.info?.(
|
|
334
455
|
`[${account.accountId}] openclaw-clawchat send complete to=${target.chatId}`,
|
|
335
456
|
);
|
|
457
|
+
return result;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const logDetachedFailure = (action: string, error: unknown) => {
|
|
461
|
+
log?.error?.(
|
|
462
|
+
`[${account.accountId}] openclaw-clawchat ${action} failed: ${String(error)}`,
|
|
463
|
+
);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const settleDetached = (action: string, promise: Promise<unknown>) => {
|
|
467
|
+
void promise.catch((error) => logDetachedFailure(action, error));
|
|
336
468
|
};
|
|
337
469
|
|
|
338
470
|
const emitFinalConsolidatedMessage = async () => {
|
|
339
471
|
if (finalEmitted) return;
|
|
340
472
|
finalEmitted = true;
|
|
473
|
+
if (!streamingMessageId) return;
|
|
341
474
|
const mergedMedia = await uploadMediaUrls(accumulatedMediaUrls.slice());
|
|
342
475
|
const mergedText = streamText.trim();
|
|
343
476
|
if (!mergedText && finalRichFragments.length === 0 && mergedMedia.length === 0) {
|
|
@@ -349,29 +482,35 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
349
482
|
log?.info?.(
|
|
350
483
|
`[${account.accountId}] openclaw-clawchat emitting consolidated final (message.reply) msg=${streamingMessageId} text_len=${mergedText.length} media=${mergedMedia.length}`,
|
|
351
484
|
);
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
485
|
+
const finalReplyCtx: OutboundReplyCtx = {
|
|
486
|
+
replyToMessageId: inboundMessageId ?? streamingMessageId,
|
|
487
|
+
replyPreviewChatId: inboundForFinalReply?.chatId ?? target.chatId,
|
|
488
|
+
replyPreviewSenderId: inboundForFinalReply?.senderId ?? target.chatId,
|
|
489
|
+
replyPreviewNickName:
|
|
490
|
+
inboundForFinalReply?.senderNickName ?? inboundForFinalReply?.senderId ?? target.chatId,
|
|
491
|
+
replyPreviewText: inboundForFinalReply?.bodyText ?? "",
|
|
492
|
+
};
|
|
359
493
|
// Streaming message_id must match the created/add/done frames so the
|
|
360
|
-
// backend can correlate the consolidated reply with the stream.
|
|
361
|
-
|
|
494
|
+
// backend can correlate the consolidated reply with the stream. Use the
|
|
495
|
+
// ackable send path so disconnects queue the final user-visible answer.
|
|
496
|
+
await sendOpenclawClawlingText({
|
|
497
|
+
client,
|
|
498
|
+
account,
|
|
499
|
+
to: target,
|
|
500
|
+
text: mergedText,
|
|
362
501
|
messageId: streamingMessageId,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
nickName:
|
|
368
|
-
inboundForFinalReply?.senderNickName ?? inboundForFinalReply?.senderId ?? target.chatId,
|
|
369
|
-
fragments: inboundForFinalReply?.bodyText
|
|
370
|
-
? [{ kind: "text", text: inboundForFinalReply.bodyText }]
|
|
371
|
-
: [],
|
|
372
|
-
},
|
|
373
|
-
body: { fragments: bodyFragments },
|
|
502
|
+
replyCtx: finalReplyCtx,
|
|
503
|
+
...(finalRichFragments.length > 0 ? { richFragments: finalRichFragments } : {}),
|
|
504
|
+
...(mergedMedia.length > 0 ? { mediaFragments: mergedMedia } : {}),
|
|
505
|
+
log,
|
|
374
506
|
});
|
|
507
|
+
updateOutbound(
|
|
508
|
+
"message.reply",
|
|
509
|
+
streamingMessageId,
|
|
510
|
+
mergedText,
|
|
511
|
+
{ target, replyCtx: replyCtx ?? null, mode: "stream-final" },
|
|
512
|
+
);
|
|
513
|
+
recordThinkingIfLinked(streamingMessageId);
|
|
375
514
|
};
|
|
376
515
|
|
|
377
516
|
const ingestFinalPayload = (payload: ReplyPayload, text: string, richFragment: Fragment | null) => {
|
|
@@ -379,10 +518,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
379
518
|
finalRichFragments.push(richFragment);
|
|
380
519
|
}
|
|
381
520
|
if (text) streamText = mergeStreamingText(streamText, text);
|
|
382
|
-
const urls =
|
|
383
|
-
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
384
|
-
...(payload.mediaUrls ?? []),
|
|
385
|
-
].filter((u): u is string => Boolean(u));
|
|
521
|
+
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
386
522
|
for (const url of urls) {
|
|
387
523
|
if (!accumulatedMediaUrls.includes(url)) accumulatedMediaUrls.push(url);
|
|
388
524
|
}
|
|
@@ -422,10 +558,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
422
558
|
deliver: async (payload: ReplyPayload, info?: { kind: "tool" | "block" | "final" }) => {
|
|
423
559
|
const richFragment = buildRichInteractionFragment(payload);
|
|
424
560
|
const text = richFragment && account.richInteractions ? "" : resolvePayloadText(payload);
|
|
425
|
-
const urls =
|
|
426
|
-
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
427
|
-
...(payload.mediaUrls ?? []),
|
|
428
|
-
].filter((u): u is string => Boolean(u));
|
|
561
|
+
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
429
562
|
log?.info?.(
|
|
430
563
|
`[${account.accountId}] openclaw-clawchat deliver kind=${info?.kind ?? "unknown"} text_len=${text.length} media_urls=${urls.length} reasoning=${payload.isReasoning === true}`,
|
|
431
564
|
);
|
|
@@ -436,7 +569,9 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
436
569
|
reasoningText = mergeStreamingText(reasoningText, text);
|
|
437
570
|
await queueStreamSnapshot();
|
|
438
571
|
} else {
|
|
439
|
-
|
|
572
|
+
reasoningText = mergeStreamingText(reasoningText, text);
|
|
573
|
+
const result = await sendStatic(text, [], [], { recordMessage: true });
|
|
574
|
+
if (result?.messageId) recordThinkingIfLinked(result.messageId);
|
|
440
575
|
}
|
|
441
576
|
return;
|
|
442
577
|
}
|
|
@@ -451,13 +586,21 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
451
586
|
);
|
|
452
587
|
// For streaming: consolidated final is emitted in onIdle after done.
|
|
453
588
|
// For static: emit immediately.
|
|
454
|
-
if (
|
|
589
|
+
if (streamingEnabled) {
|
|
590
|
+
if (text.trim()) {
|
|
591
|
+
await queueStreamSnapshot();
|
|
592
|
+
} else if (richFragment || urls.length > 0) {
|
|
593
|
+
openSessionIfNeeded();
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
455
596
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
456
|
-
await sendStatic(
|
|
597
|
+
const result = await sendStatic(
|
|
457
598
|
text,
|
|
458
599
|
mediaFragments,
|
|
459
600
|
richFragment && account.richInteractions ? ([richFragment] as unknown as Fragment[]) : [],
|
|
601
|
+
{ recordMessage: true },
|
|
460
602
|
);
|
|
603
|
+
if (result?.messageId) recordThinkingIfLinked(result.messageId);
|
|
461
604
|
}
|
|
462
605
|
return;
|
|
463
606
|
}
|
|
@@ -471,14 +614,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
471
614
|
log?.info?.(
|
|
472
615
|
`[${account.accountId}] openclaw-clawchat mid-stream media emitted as separate message (count=${mediaFragments.length})`,
|
|
473
616
|
);
|
|
474
|
-
await
|
|
475
|
-
client,
|
|
476
|
-
account,
|
|
477
|
-
to: target,
|
|
478
|
-
text: "",
|
|
479
|
-
mediaFragments,
|
|
480
|
-
log,
|
|
481
|
-
});
|
|
617
|
+
await sendStatic("", mediaFragments, [], { recordMessage: true });
|
|
482
618
|
}
|
|
483
619
|
}
|
|
484
620
|
} else {
|
|
@@ -486,7 +622,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
486
622
|
const richFragments =
|
|
487
623
|
richFragment && account.richInteractions ? ([richFragment] as unknown as Fragment[]) : [];
|
|
488
624
|
if (text.trim() || mediaFragments.length > 0 || richFragments.length > 0) {
|
|
489
|
-
await sendStatic(text, mediaFragments, richFragments);
|
|
625
|
+
await sendStatic(text, mediaFragments, richFragments, { recordMessage: true });
|
|
490
626
|
}
|
|
491
627
|
}
|
|
492
628
|
},
|
|
@@ -496,24 +632,29 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
496
632
|
`[${account.accountId}] openclaw-clawchat ${info.kind} reply failed: ${errorText}`,
|
|
497
633
|
);
|
|
498
634
|
if (!streamingEnabled) {
|
|
499
|
-
void sendStatic(errorText);
|
|
500
635
|
return;
|
|
501
636
|
}
|
|
502
637
|
runFailed = true;
|
|
503
638
|
if (streamingSession && !streamingClosed) {
|
|
504
|
-
|
|
639
|
+
settleDetached(
|
|
640
|
+
"stream failure close",
|
|
641
|
+
closeStreamingSession("fail", CLIENT_SAFE_REPLY_FAILURE_TEXT),
|
|
642
|
+
);
|
|
505
643
|
return;
|
|
506
644
|
}
|
|
507
645
|
if (streamingClosed) return;
|
|
508
646
|
streamingClosed = true;
|
|
509
647
|
if (!streamingMessageId) streamingMessageId = mintStreamingMessageId();
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
648
|
+
settleDetached(
|
|
649
|
+
"stream failure send",
|
|
650
|
+
sendStreamingFailure({
|
|
651
|
+
client,
|
|
652
|
+
routing,
|
|
653
|
+
messageId: streamingMessageId,
|
|
654
|
+
currentSequence: 0,
|
|
655
|
+
reason: CLIENT_SAFE_REPLY_FAILURE_TEXT,
|
|
656
|
+
}),
|
|
657
|
+
);
|
|
517
658
|
},
|
|
518
659
|
onIdle: async () => {
|
|
519
660
|
if (runDone) return;
|
|
@@ -551,6 +692,7 @@ export function createOpenclawClawlingReplyDispatcher(options: ReplyDispatcherOp
|
|
|
551
692
|
dispatcher: base.dispatcher,
|
|
552
693
|
replyOptions: {
|
|
553
694
|
...base.replyOptions,
|
|
695
|
+
sourceReplyDeliveryMode: "automatic",
|
|
554
696
|
...streamingHooks,
|
|
555
697
|
},
|
|
556
698
|
markDispatchIdle: base.markDispatchIdle,
|