@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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { interactiveReplyToPresentation, renderMessagePresentationFallbackText, } from "openclaw/plugin-sdk/interactive-runtime";
|
|
2
|
+
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
2
3
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
3
4
|
import { openBufferedStreamingSession, mergeStreamingText, } from "./buffered-stream.js";
|
|
4
|
-
import { emitFinalStreamReply } from "./client.js";
|
|
5
|
-
import { textToFragments } from "./message-mapper.js";
|
|
6
5
|
import { uploadOutboundMedia } from "./media-runtime.js";
|
|
7
|
-
import { sendOpenclawClawlingText } from "./outbound.js";
|
|
6
|
+
import { sendOpenclawClawlingText, } from "./outbound.js";
|
|
8
7
|
import { sendStreamingFailure } from "./streaming.js";
|
|
8
|
+
const CLIENT_SAFE_REPLY_FAILURE_TEXT = "OpenClaw could not complete this reply.";
|
|
9
9
|
function normalizeReplyErrorText(error) {
|
|
10
10
|
const raw = String(error);
|
|
11
11
|
const retryWrapped = raw.match(/^Error: Retry failed for delivery [^:]+:\s*(.+)$/s);
|
|
@@ -117,7 +117,7 @@ function resolvePayloadText(payload) {
|
|
|
117
117
|
* `message.reply` per deliver with text + media.
|
|
118
118
|
*/
|
|
119
119
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
120
|
-
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, inboundForFinalReply, log, } = options;
|
|
120
|
+
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, inboundForFinalReply, store, log, } = options;
|
|
121
121
|
const routing = { chatId: target.chatId, chatType: target.chatType };
|
|
122
122
|
const humanDelay = runtime.channel.reply.resolveHumanDelayConfig(cfg, account.userId);
|
|
123
123
|
const streamingEnabled = account.replyMode === "stream" && !replyCtx;
|
|
@@ -151,16 +151,87 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
151
151
|
let streamingClosed = false;
|
|
152
152
|
let runFailed = false;
|
|
153
153
|
let runDone = false;
|
|
154
|
+
let streamClaimAttempted = false;
|
|
154
155
|
// `streamCreatedEmitted` is the authoritative guard: once a `message.created`
|
|
155
156
|
// has been emitted for this dispatcher instance, never emit another — even
|
|
156
157
|
// if `onReplyStart` fires again or a pre-onReplyStart `onPartialReply`
|
|
157
158
|
// raced the lazy open path.
|
|
158
159
|
let streamCreatedEmitted = false;
|
|
160
|
+
const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
|
|
161
|
+
const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
|
|
162
|
+
const claimOutbound = (eventType, messageId, text, raw) => {
|
|
163
|
+
if (!store || !messageId)
|
|
164
|
+
return null;
|
|
165
|
+
try {
|
|
166
|
+
return store.claimMessageOnce({
|
|
167
|
+
platform: "openclaw",
|
|
168
|
+
accountId: account.accountId,
|
|
169
|
+
kind: "message",
|
|
170
|
+
direction: "outbound",
|
|
171
|
+
eventType,
|
|
172
|
+
chatId: target.chatId,
|
|
173
|
+
messageId,
|
|
174
|
+
text,
|
|
175
|
+
raw,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound claim failed`);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const updateOutbound = (eventType, messageId, text, raw) => {
|
|
184
|
+
if (!store || !messageId)
|
|
185
|
+
return;
|
|
186
|
+
try {
|
|
187
|
+
store.updateMessageByIdentity({
|
|
188
|
+
accountId: account.accountId,
|
|
189
|
+
kind: "message",
|
|
190
|
+
direction: "outbound",
|
|
191
|
+
eventType,
|
|
192
|
+
chatId: target.chatId,
|
|
193
|
+
messageId,
|
|
194
|
+
text,
|
|
195
|
+
raw,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound update failed; continuing`);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const recordOutbound = (kind, messageId, text) => {
|
|
203
|
+
if (!store || !messageId)
|
|
204
|
+
return;
|
|
205
|
+
try {
|
|
206
|
+
store.insertMessage({
|
|
207
|
+
platform: "openclaw",
|
|
208
|
+
accountId: account.accountId,
|
|
209
|
+
kind,
|
|
210
|
+
direction: "outbound",
|
|
211
|
+
eventType: outboundEventType(),
|
|
212
|
+
chatId: target.chatId,
|
|
213
|
+
messageId,
|
|
214
|
+
text,
|
|
215
|
+
raw: outboundRaw(),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat sqlite outbound insert failed; continuing`);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const recordThinkingIfLinked = (messageId) => {
|
|
223
|
+
const thinkingText = reasoningText.trim();
|
|
224
|
+
if (!thinkingText)
|
|
225
|
+
return;
|
|
226
|
+
recordOutbound("thinking", messageId, thinkingText);
|
|
227
|
+
reasoningText = "";
|
|
228
|
+
};
|
|
159
229
|
const mintStreamingMessageId = () => `${account.userId}-stream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
230
|
+
const mintStaticMessageId = () => `${account.userId}-msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
160
231
|
const openSessionIfNeeded = () => {
|
|
161
|
-
if (!streamingEnabled || streamingSession || streamCreatedEmitted)
|
|
232
|
+
if (!streamingEnabled || streamingSession || streamCreatedEmitted || streamClaimAttempted)
|
|
162
233
|
return;
|
|
163
|
-
|
|
234
|
+
streamClaimAttempted = true;
|
|
164
235
|
// Mint a fresh agent-side message_id at `message.created` time. All
|
|
165
236
|
// subsequent `message.add` / `message.done` / `message.reply` frames for
|
|
166
237
|
// this stream reuse it. Once the stream finalizes (done or reply), this
|
|
@@ -169,12 +240,20 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
169
240
|
// `replyTo.msgId`; keeping the two distinct avoids the agent's reply
|
|
170
241
|
// frames shadowing the user turn they answer.
|
|
171
242
|
streamingMessageId = mintStreamingMessageId();
|
|
243
|
+
const claimed = claimOutbound("message.created", streamingMessageId, "", { target, replyCtx: replyCtx ?? null, mode: "stream" });
|
|
244
|
+
if (claimed !== true) {
|
|
245
|
+
streamCreatedEmitted = false;
|
|
246
|
+
streamingMessageId = "";
|
|
247
|
+
log?.[claimed === false ? "info" : "error"]?.(`[${account.accountId}] openclaw-clawchat stream outbound skipped reason=${claimed === false ? "duplicate" : "claim_unavailable"}`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
streamCreatedEmitted = true;
|
|
172
251
|
streamingSession = openBufferedStreamingSession({
|
|
173
252
|
client,
|
|
174
253
|
routing,
|
|
175
254
|
sender: {
|
|
176
255
|
id: account.userId,
|
|
177
|
-
type:
|
|
256
|
+
type: "direct",
|
|
178
257
|
nick_name: account.userId,
|
|
179
258
|
},
|
|
180
259
|
messageId: streamingMessageId,
|
|
@@ -215,26 +294,49 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
215
294
|
log?.info?.(`[${account.accountId}] openclaw-clawchat streaming closed msg=${streamingMessageId} reason=${reason ?? "done"}`);
|
|
216
295
|
};
|
|
217
296
|
// ----- Static send ------------------------------------------------------
|
|
218
|
-
const sendStatic = async (text, mediaFragments = [], richFragments = []) => {
|
|
297
|
+
const sendStatic = async (text, mediaFragments = [], richFragments = [], options = {}) => {
|
|
219
298
|
if (!text.trim() && mediaFragments.length === 0 && richFragments.length === 0)
|
|
220
|
-
return;
|
|
299
|
+
return null;
|
|
221
300
|
log?.info?.(`[${account.accountId}] openclaw-clawchat sending static text_len=${text.length} media=${mediaFragments.length} rich=${richFragments.length} to=${target.chatId}`);
|
|
222
|
-
|
|
301
|
+
const messageId = mintStaticMessageId();
|
|
302
|
+
const raw = { target, replyCtx: replyCtx ?? null, mode: "static" };
|
|
303
|
+
const claimed = options.recordMessage
|
|
304
|
+
? claimOutbound(outboundEventType(), messageId, text, raw)
|
|
305
|
+
: true;
|
|
306
|
+
if (claimed === false) {
|
|
307
|
+
log?.info?.(`[${account.accountId}] openclaw-clawchat outbound duplicate skipped msg=${messageId}`);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (claimed === null) {
|
|
311
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat outbound skipped msg=${messageId} reason=claim_unavailable`);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
const result = await sendOpenclawClawlingText({
|
|
223
315
|
client,
|
|
224
316
|
account,
|
|
225
317
|
to: target,
|
|
226
318
|
text,
|
|
319
|
+
messageId,
|
|
227
320
|
...(replyCtx ? { replyCtx } : {}),
|
|
228
321
|
...(richFragments.length > 0 ? { richFragments } : {}),
|
|
229
322
|
...(mediaFragments.length > 0 ? { mediaFragments } : {}),
|
|
230
323
|
log,
|
|
231
324
|
});
|
|
232
325
|
log?.info?.(`[${account.accountId}] openclaw-clawchat send complete to=${target.chatId}`);
|
|
326
|
+
return result;
|
|
327
|
+
};
|
|
328
|
+
const logDetachedFailure = (action, error) => {
|
|
329
|
+
log?.error?.(`[${account.accountId}] openclaw-clawchat ${action} failed: ${String(error)}`);
|
|
330
|
+
};
|
|
331
|
+
const settleDetached = (action, promise) => {
|
|
332
|
+
void promise.catch((error) => logDetachedFailure(action, error));
|
|
233
333
|
};
|
|
234
334
|
const emitFinalConsolidatedMessage = async () => {
|
|
235
335
|
if (finalEmitted)
|
|
236
336
|
return;
|
|
237
337
|
finalEmitted = true;
|
|
338
|
+
if (!streamingMessageId)
|
|
339
|
+
return;
|
|
238
340
|
const mergedMedia = await uploadMediaUrls(accumulatedMediaUrls.slice());
|
|
239
341
|
const mergedText = streamText.trim();
|
|
240
342
|
if (!mergedText && finalRichFragments.length === 0 && mergedMedia.length === 0) {
|
|
@@ -242,28 +344,29 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
242
344
|
return;
|
|
243
345
|
}
|
|
244
346
|
log?.info?.(`[${account.accountId}] openclaw-clawchat emitting consolidated final (message.reply) msg=${streamingMessageId} text_len=${mergedText.length} media=${mergedMedia.length}`);
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
347
|
+
const finalReplyCtx = {
|
|
348
|
+
replyToMessageId: inboundMessageId ?? streamingMessageId,
|
|
349
|
+
replyPreviewChatId: inboundForFinalReply?.chatId ?? target.chatId,
|
|
350
|
+
replyPreviewSenderId: inboundForFinalReply?.senderId ?? target.chatId,
|
|
351
|
+
replyPreviewNickName: inboundForFinalReply?.senderNickName ?? inboundForFinalReply?.senderId ?? target.chatId,
|
|
352
|
+
replyPreviewText: inboundForFinalReply?.bodyText ?? "",
|
|
353
|
+
};
|
|
252
354
|
// Streaming message_id must match the created/add/done frames so the
|
|
253
|
-
// backend can correlate the consolidated reply with the stream.
|
|
254
|
-
|
|
355
|
+
// backend can correlate the consolidated reply with the stream. Use the
|
|
356
|
+
// ackable send path so disconnects queue the final user-visible answer.
|
|
357
|
+
await sendOpenclawClawlingText({
|
|
358
|
+
client,
|
|
359
|
+
account,
|
|
360
|
+
to: target,
|
|
361
|
+
text: mergedText,
|
|
255
362
|
messageId: streamingMessageId,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
nickName: inboundForFinalReply?.senderNickName ?? inboundForFinalReply?.senderId ?? target.chatId,
|
|
261
|
-
fragments: inboundForFinalReply?.bodyText
|
|
262
|
-
? [{ kind: "text", text: inboundForFinalReply.bodyText }]
|
|
263
|
-
: [],
|
|
264
|
-
},
|
|
265
|
-
body: { fragments: bodyFragments },
|
|
363
|
+
replyCtx: finalReplyCtx,
|
|
364
|
+
...(finalRichFragments.length > 0 ? { richFragments: finalRichFragments } : {}),
|
|
365
|
+
...(mergedMedia.length > 0 ? { mediaFragments: mergedMedia } : {}),
|
|
366
|
+
log,
|
|
266
367
|
});
|
|
368
|
+
updateOutbound("message.reply", streamingMessageId, mergedText, { target, replyCtx: replyCtx ?? null, mode: "stream-final" });
|
|
369
|
+
recordThinkingIfLinked(streamingMessageId);
|
|
267
370
|
};
|
|
268
371
|
const ingestFinalPayload = (payload, text, richFragment) => {
|
|
269
372
|
if (richFragment && account.richInteractions) {
|
|
@@ -271,10 +374,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
271
374
|
}
|
|
272
375
|
if (text)
|
|
273
376
|
streamText = mergeStreamingText(streamText, text);
|
|
274
|
-
const urls =
|
|
275
|
-
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
276
|
-
...(payload.mediaUrls ?? []),
|
|
277
|
-
].filter((u) => Boolean(u));
|
|
377
|
+
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
278
378
|
for (const url of urls) {
|
|
279
379
|
if (!accumulatedMediaUrls.includes(url))
|
|
280
380
|
accumulatedMediaUrls.push(url);
|
|
@@ -313,10 +413,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
313
413
|
deliver: async (payload, info) => {
|
|
314
414
|
const richFragment = buildRichInteractionFragment(payload);
|
|
315
415
|
const text = richFragment && account.richInteractions ? "" : resolvePayloadText(payload);
|
|
316
|
-
const urls =
|
|
317
|
-
...(payload.mediaUrl ? [payload.mediaUrl] : []),
|
|
318
|
-
...(payload.mediaUrls ?? []),
|
|
319
|
-
].filter((u) => Boolean(u));
|
|
416
|
+
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
320
417
|
log?.info?.(`[${account.accountId}] openclaw-clawchat deliver kind=${info?.kind ?? "unknown"} text_len=${text.length} media_urls=${urls.length} reasoning=${payload.isReasoning === true}`);
|
|
321
418
|
if (payload.isReasoning) {
|
|
322
419
|
if (!account.forwardThinking)
|
|
@@ -326,7 +423,10 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
326
423
|
await queueStreamSnapshot();
|
|
327
424
|
}
|
|
328
425
|
else {
|
|
329
|
-
|
|
426
|
+
reasoningText = mergeStreamingText(reasoningText, text);
|
|
427
|
+
const result = await sendStatic(text, [], [], { recordMessage: true });
|
|
428
|
+
if (result?.messageId)
|
|
429
|
+
recordThinkingIfLinked(result.messageId);
|
|
330
430
|
}
|
|
331
431
|
return;
|
|
332
432
|
}
|
|
@@ -336,9 +436,19 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
336
436
|
ingestFinalPayload(payload, text, richFragment && account.richInteractions ? richFragment : null);
|
|
337
437
|
// For streaming: consolidated final is emitted in onIdle after done.
|
|
338
438
|
// For static: emit immediately.
|
|
339
|
-
if (
|
|
439
|
+
if (streamingEnabled) {
|
|
440
|
+
if (text.trim()) {
|
|
441
|
+
await queueStreamSnapshot();
|
|
442
|
+
}
|
|
443
|
+
else if (richFragment || urls.length > 0) {
|
|
444
|
+
openSessionIfNeeded();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
340
448
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
341
|
-
await sendStatic(text, mediaFragments, richFragment && account.richInteractions ? [richFragment] : []);
|
|
449
|
+
const result = await sendStatic(text, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
|
|
450
|
+
if (result?.messageId)
|
|
451
|
+
recordThinkingIfLinked(result.messageId);
|
|
342
452
|
}
|
|
343
453
|
return;
|
|
344
454
|
}
|
|
@@ -350,14 +460,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
350
460
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
351
461
|
if (mediaFragments.length > 0) {
|
|
352
462
|
log?.info?.(`[${account.accountId}] openclaw-clawchat mid-stream media emitted as separate message (count=${mediaFragments.length})`);
|
|
353
|
-
await
|
|
354
|
-
client,
|
|
355
|
-
account,
|
|
356
|
-
to: target,
|
|
357
|
-
text: "",
|
|
358
|
-
mediaFragments,
|
|
359
|
-
log,
|
|
360
|
-
});
|
|
463
|
+
await sendStatic("", mediaFragments, [], { recordMessage: true });
|
|
361
464
|
}
|
|
362
465
|
}
|
|
363
466
|
}
|
|
@@ -365,7 +468,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
365
468
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
366
469
|
const richFragments = richFragment && account.richInteractions ? [richFragment] : [];
|
|
367
470
|
if (text.trim() || mediaFragments.length > 0 || richFragments.length > 0) {
|
|
368
|
-
await sendStatic(text, mediaFragments, richFragments);
|
|
471
|
+
await sendStatic(text, mediaFragments, richFragments, { recordMessage: true });
|
|
369
472
|
}
|
|
370
473
|
}
|
|
371
474
|
},
|
|
@@ -373,12 +476,11 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
373
476
|
const errorText = normalizeReplyErrorText(error);
|
|
374
477
|
log?.error?.(`[${account.accountId}] openclaw-clawchat ${info.kind} reply failed: ${errorText}`);
|
|
375
478
|
if (!streamingEnabled) {
|
|
376
|
-
void sendStatic(errorText);
|
|
377
479
|
return;
|
|
378
480
|
}
|
|
379
481
|
runFailed = true;
|
|
380
482
|
if (streamingSession && !streamingClosed) {
|
|
381
|
-
|
|
483
|
+
settleDetached("stream failure close", closeStreamingSession("fail", CLIENT_SAFE_REPLY_FAILURE_TEXT));
|
|
382
484
|
return;
|
|
383
485
|
}
|
|
384
486
|
if (streamingClosed)
|
|
@@ -386,13 +488,13 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
386
488
|
streamingClosed = true;
|
|
387
489
|
if (!streamingMessageId)
|
|
388
490
|
streamingMessageId = mintStreamingMessageId();
|
|
389
|
-
|
|
491
|
+
settleDetached("stream failure send", sendStreamingFailure({
|
|
390
492
|
client,
|
|
391
493
|
routing,
|
|
392
494
|
messageId: streamingMessageId,
|
|
393
495
|
currentSequence: 0,
|
|
394
|
-
reason:
|
|
395
|
-
});
|
|
496
|
+
reason: CLIENT_SAFE_REPLY_FAILURE_TEXT,
|
|
497
|
+
}));
|
|
396
498
|
},
|
|
397
499
|
onIdle: async () => {
|
|
398
500
|
if (runDone)
|
|
@@ -433,6 +535,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
433
535
|
dispatcher: base.dispatcher,
|
|
434
536
|
replyOptions: {
|
|
435
537
|
...base.replyOptions,
|
|
538
|
+
sourceReplyDeliveryMode: "automatic",
|
|
436
539
|
...streamingHooks,
|
|
437
540
|
},
|
|
438
541
|
markDispatchIdle: base.markDispatchIdle,
|