@newbase-clawchat/openclaw-clawchat 2026.5.12-2 → 2026.5.12-21
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/README.md +39 -17
- package/dist/index.js +3 -1
- package/dist/src/api-client.js +71 -12
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/channel.js +5 -5
- package/dist/src/channel.setup.js +4 -17
- package/dist/src/clawchat-memory.js +290 -0
- package/dist/src/clawchat-metadata.js +235 -0
- package/dist/src/client.js +31 -93
- package/dist/src/commands.js +3 -3
- package/dist/src/config.js +58 -3
- package/dist/src/group-message-coalescer.js +107 -0
- package/dist/src/inbound.js +24 -28
- package/dist/src/login.runtime.js +82 -19
- package/dist/src/media-runtime.js +2 -3
- package/dist/src/message-mapper.js +1 -1
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +281 -56
- package/dist/src/plugin-prompts.js +76 -0
- package/dist/src/profile-prompt.js +150 -0
- package/dist/src/profile-sync.js +169 -0
- package/dist/src/prompt-injection.js +25 -0
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +2 -2
- package/dist/src/reply-dispatcher.js +143 -40
- package/dist/src/runtime.js +813 -109
- package/dist/src/storage.js +636 -0
- package/dist/src/tools-schema.js +70 -10
- package/dist/src/tools.js +600 -112
- package/dist/src/ws-alignment.js +8 -0
- package/dist/src/ws-client.js +588 -0
- package/index.ts +6 -1
- package/openclaw.plugin.json +44 -4
- package/package.json +4 -3
- package/prompts/platform.md +7 -0
- package/skills/clawchat/SKILL.md +90 -0
- package/src/api-client.test.ts +360 -15
- package/src/api-client.ts +127 -25
- package/src/api-types.test-d.ts +12 -0
- package/src/api-types.ts +71 -4
- package/src/buffered-stream.test.ts +1 -1
- package/src/buffered-stream.ts +1 -1
- package/src/channel.outbound.test.ts +270 -60
- package/src/channel.setup.ts +9 -18
- package/src/channel.test.ts +33 -25
- package/src/channel.ts +5 -7
- package/src/clawchat-memory.test.ts +372 -0
- package/src/clawchat-memory.ts +363 -0
- package/src/clawchat-metadata.test.ts +350 -0
- package/src/clawchat-metadata.ts +352 -0
- package/src/client.test.ts +57 -48
- package/src/client.ts +37 -129
- package/src/commands.test.ts +2 -2
- package/src/commands.ts +3 -3
- package/src/config.test.ts +169 -4
- package/src/config.ts +86 -6
- package/src/group-message-coalescer.test.ts +223 -0
- package/src/group-message-coalescer.ts +154 -0
- package/src/inbound.test.ts +106 -19
- package/src/inbound.ts +31 -35
- package/src/login.runtime.test.ts +294 -11
- package/src/login.runtime.ts +90 -21
- package/src/manifest.test.ts +86 -14
- package/src/media-runtime.test.ts +31 -2
- package/src/media-runtime.ts +7 -10
- 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 +811 -95
- package/src/outbound.ts +332 -65
- package/src/plugin-entry.test.ts +3 -1
- package/src/plugin-prompts.test.ts +78 -0
- package/src/plugin-prompts.ts +92 -0
- package/src/profile-prompt.test.ts +435 -0
- package/src/profile-prompt.ts +208 -0
- package/src/profile-sync.test.ts +611 -0
- package/src/profile-sync.ts +268 -0
- package/src/prompt-injection.test.ts +39 -0
- package/src/prompt-injection.ts +45 -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.ts +2 -2
- package/src/reply-dispatcher.test.ts +720 -135
- package/src/reply-dispatcher.ts +174 -42
- package/src/runtime.test.ts +3884 -337
- package/src/runtime.ts +956 -128
- package/src/storage.test.ts +692 -0
- package/src/storage.ts +989 -0
- package/src/streaming.test.ts +1 -1
- package/src/streaming.ts +1 -1
- package/src/tools-schema.ts +115 -13
- package/src/tools.test.ts +501 -10
- package/src/tools.ts +739 -133
- package/src/ws-alignment.ts +9 -0
- package/src/ws-client.test.ts +1218 -0
- package/src/ws-client.ts +662 -0
|
@@ -2,10 +2,8 @@ import { interactiveReplyToPresentation, renderMessagePresentationFallbackText,
|
|
|
2
2
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
3
3
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
4
4
|
import { openBufferedStreamingSession, mergeStreamingText, } from "./buffered-stream.js";
|
|
5
|
-
import { emitFinalStreamReply } from "./client.js";
|
|
6
|
-
import { textToFragments } from "./message-mapper.js";
|
|
7
5
|
import { uploadOutboundMedia } from "./media-runtime.js";
|
|
8
|
-
import { sendOpenclawClawlingText } from "./outbound.js";
|
|
6
|
+
import { sendOpenclawClawlingText, } from "./outbound.js";
|
|
9
7
|
import { sendStreamingFailure } from "./streaming.js";
|
|
10
8
|
const CLIENT_SAFE_REPLY_FAILURE_TEXT = "OpenClaw could not complete this reply.";
|
|
11
9
|
function normalizeReplyErrorText(error) {
|
|
@@ -119,7 +117,7 @@ function resolvePayloadText(payload) {
|
|
|
119
117
|
* `message.reply` per deliver with text + media.
|
|
120
118
|
*/
|
|
121
119
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
122
|
-
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, inboundForFinalReply, log, } = options;
|
|
120
|
+
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, inboundForFinalReply, store, log, } = options;
|
|
123
121
|
const routing = { chatId: target.chatId, chatType: target.chatType };
|
|
124
122
|
const humanDelay = runtime.channel.reply.resolveHumanDelayConfig(cfg, account.userId);
|
|
125
123
|
const streamingEnabled = account.replyMode === "stream" && !replyCtx;
|
|
@@ -153,16 +151,87 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
153
151
|
let streamingClosed = false;
|
|
154
152
|
let runFailed = false;
|
|
155
153
|
let runDone = false;
|
|
154
|
+
let streamClaimAttempted = false;
|
|
156
155
|
// `streamCreatedEmitted` is the authoritative guard: once a `message.created`
|
|
157
156
|
// has been emitted for this dispatcher instance, never emit another — even
|
|
158
157
|
// if `onReplyStart` fires again or a pre-onReplyStart `onPartialReply`
|
|
159
158
|
// raced the lazy open path.
|
|
160
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
|
+
};
|
|
161
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)}`;
|
|
162
231
|
const openSessionIfNeeded = () => {
|
|
163
|
-
if (!streamingEnabled || streamingSession || streamCreatedEmitted)
|
|
232
|
+
if (!streamingEnabled || streamingSession || streamCreatedEmitted || streamClaimAttempted)
|
|
164
233
|
return;
|
|
165
|
-
|
|
234
|
+
streamClaimAttempted = true;
|
|
166
235
|
// Mint a fresh agent-side message_id at `message.created` time. All
|
|
167
236
|
// subsequent `message.add` / `message.done` / `message.reply` frames for
|
|
168
237
|
// this stream reuse it. Once the stream finalizes (done or reply), this
|
|
@@ -171,6 +240,14 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
171
240
|
// `replyTo.msgId`; keeping the two distinct avoids the agent's reply
|
|
172
241
|
// frames shadowing the user turn they answer.
|
|
173
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;
|
|
174
251
|
streamingSession = openBufferedStreamingSession({
|
|
175
252
|
client,
|
|
176
253
|
routing,
|
|
@@ -217,21 +294,36 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
217
294
|
log?.info?.(`[${account.accountId}] openclaw-clawchat streaming closed msg=${streamingMessageId} reason=${reason ?? "done"}`);
|
|
218
295
|
};
|
|
219
296
|
// ----- Static send ------------------------------------------------------
|
|
220
|
-
const sendStatic = async (text, mediaFragments = [], richFragments = []) => {
|
|
297
|
+
const sendStatic = async (text, mediaFragments = [], richFragments = [], options = {}) => {
|
|
221
298
|
if (!text.trim() && mediaFragments.length === 0 && richFragments.length === 0)
|
|
222
|
-
return;
|
|
299
|
+
return null;
|
|
223
300
|
log?.info?.(`[${account.accountId}] openclaw-clawchat sending static text_len=${text.length} media=${mediaFragments.length} rich=${richFragments.length} to=${target.chatId}`);
|
|
224
|
-
|
|
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({
|
|
225
315
|
client,
|
|
226
316
|
account,
|
|
227
317
|
to: target,
|
|
228
318
|
text,
|
|
319
|
+
messageId,
|
|
229
320
|
...(replyCtx ? { replyCtx } : {}),
|
|
230
321
|
...(richFragments.length > 0 ? { richFragments } : {}),
|
|
231
322
|
...(mediaFragments.length > 0 ? { mediaFragments } : {}),
|
|
232
323
|
log,
|
|
233
324
|
});
|
|
234
325
|
log?.info?.(`[${account.accountId}] openclaw-clawchat send complete to=${target.chatId}`);
|
|
326
|
+
return result;
|
|
235
327
|
};
|
|
236
328
|
const logDetachedFailure = (action, error) => {
|
|
237
329
|
log?.error?.(`[${account.accountId}] openclaw-clawchat ${action} failed: ${String(error)}`);
|
|
@@ -243,6 +335,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
243
335
|
if (finalEmitted)
|
|
244
336
|
return;
|
|
245
337
|
finalEmitted = true;
|
|
338
|
+
if (!streamingMessageId)
|
|
339
|
+
return;
|
|
246
340
|
const mergedMedia = await uploadMediaUrls(accumulatedMediaUrls.slice());
|
|
247
341
|
const mergedText = streamText.trim();
|
|
248
342
|
if (!mergedText && finalRichFragments.length === 0 && mergedMedia.length === 0) {
|
|
@@ -250,28 +344,29 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
250
344
|
return;
|
|
251
345
|
}
|
|
252
346
|
log?.info?.(`[${account.accountId}] openclaw-clawchat emitting consolidated final (message.reply) msg=${streamingMessageId} text_len=${mergedText.length} media=${mergedMedia.length}`);
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
};
|
|
260
354
|
// Streaming message_id must match the created/add/done frames so the
|
|
261
|
-
// backend can correlate the consolidated reply with the stream.
|
|
262
|
-
|
|
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,
|
|
263
362
|
messageId: streamingMessageId,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
nickName: inboundForFinalReply?.senderNickName ?? inboundForFinalReply?.senderId ?? target.chatId,
|
|
269
|
-
fragments: inboundForFinalReply?.bodyText
|
|
270
|
-
? [{ kind: "text", text: inboundForFinalReply.bodyText }]
|
|
271
|
-
: [],
|
|
272
|
-
},
|
|
273
|
-
body: { fragments: bodyFragments },
|
|
363
|
+
replyCtx: finalReplyCtx,
|
|
364
|
+
...(finalRichFragments.length > 0 ? { richFragments: finalRichFragments } : {}),
|
|
365
|
+
...(mergedMedia.length > 0 ? { mediaFragments: mergedMedia } : {}),
|
|
366
|
+
log,
|
|
274
367
|
});
|
|
368
|
+
updateOutbound("message.reply", streamingMessageId, mergedText, { target, replyCtx: replyCtx ?? null, mode: "stream-final" });
|
|
369
|
+
recordThinkingIfLinked(streamingMessageId);
|
|
275
370
|
};
|
|
276
371
|
const ingestFinalPayload = (payload, text, richFragment) => {
|
|
277
372
|
if (richFragment && account.richInteractions) {
|
|
@@ -328,7 +423,10 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
328
423
|
await queueStreamSnapshot();
|
|
329
424
|
}
|
|
330
425
|
else {
|
|
331
|
-
|
|
426
|
+
reasoningText = mergeStreamingText(reasoningText, text);
|
|
427
|
+
const result = await sendStatic(text, [], [], { recordMessage: true });
|
|
428
|
+
if (result?.messageId)
|
|
429
|
+
recordThinkingIfLinked(result.messageId);
|
|
332
430
|
}
|
|
333
431
|
return;
|
|
334
432
|
}
|
|
@@ -338,9 +436,19 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
338
436
|
ingestFinalPayload(payload, text, richFragment && account.richInteractions ? richFragment : null);
|
|
339
437
|
// For streaming: consolidated final is emitted in onIdle after done.
|
|
340
438
|
// For static: emit immediately.
|
|
341
|
-
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 {
|
|
342
448
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
343
|
-
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);
|
|
344
452
|
}
|
|
345
453
|
return;
|
|
346
454
|
}
|
|
@@ -352,14 +460,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
352
460
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
353
461
|
if (mediaFragments.length > 0) {
|
|
354
462
|
log?.info?.(`[${account.accountId}] openclaw-clawchat mid-stream media emitted as separate message (count=${mediaFragments.length})`);
|
|
355
|
-
await
|
|
356
|
-
client,
|
|
357
|
-
account,
|
|
358
|
-
to: target,
|
|
359
|
-
text: "",
|
|
360
|
-
mediaFragments,
|
|
361
|
-
log,
|
|
362
|
-
});
|
|
463
|
+
await sendStatic("", mediaFragments, [], { recordMessage: true });
|
|
363
464
|
}
|
|
364
465
|
}
|
|
365
466
|
}
|
|
@@ -367,7 +468,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
367
468
|
const mediaFragments = await uploadMediaUrls(urls);
|
|
368
469
|
const richFragments = richFragment && account.richInteractions ? [richFragment] : [];
|
|
369
470
|
if (text.trim() || mediaFragments.length > 0 || richFragments.length > 0) {
|
|
370
|
-
await sendStatic(text, mediaFragments, richFragments);
|
|
471
|
+
await sendStatic(text, mediaFragments, richFragments, { recordMessage: true });
|
|
371
472
|
}
|
|
372
473
|
}
|
|
373
474
|
},
|
|
@@ -434,6 +535,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
434
535
|
dispatcher: base.dispatcher,
|
|
435
536
|
replyOptions: {
|
|
436
537
|
...base.replyOptions,
|
|
538
|
+
sourceReplyDeliveryMode: "automatic",
|
|
539
|
+
...(streamingEnabled ? { disableBlockStreaming: false } : {}),
|
|
437
540
|
...streamingHooks,
|
|
438
541
|
},
|
|
439
542
|
markDispatchIdle: base.markDispatchIdle,
|