@clawling/clawchat-plugin-openclaw 2026.5.12-32 → 2026.5.12-38
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/api-client.js +10 -6
- package/dist/src/client.js +9 -1
- package/dist/src/commands.js +1 -1
- package/dist/src/login.runtime.js +2 -0
- package/dist/src/reply-dispatcher.js +289 -20
- package/dist/src/runtime.js +41 -2
- package/dist/src/tools-schema.js +0 -5
- package/dist/src/tools.js +2 -36
- package/openclaw.plugin.json +0 -1
- package/package.json +1 -1
- package/skills/clawchat/SKILL.md +0 -1
- package/src/api-client.ts +13 -7
- package/src/client.ts +10 -2
- package/src/commands.ts +1 -1
- package/src/login.runtime.ts +2 -0
- package/src/reply-dispatcher.ts +304 -19
- package/src/runtime.ts +54 -1
- package/src/tools-schema.ts +0 -8
- package/src/tools.ts +1 -46
package/dist/src/api-client.js
CHANGED
|
@@ -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) {
|
package/dist/src/client.js
CHANGED
|
@@ -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
|
|
14
|
+
deviceId: resolveOpenclawClawlingDeviceId(account),
|
|
7
15
|
...(overrides.transport ? { transport: overrides.transport } : {}),
|
|
8
16
|
reconnect: {
|
|
9
17
|
enabled: true,
|
package/dist/src/commands.js
CHANGED
|
@@ -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
|
|
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,162 @@ 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
|
-
*
|
|
112
|
-
*
|
|
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" && !isGroupTarget;
|
|
118
263
|
const ownerDirectTarget = () => {
|
|
119
264
|
const ownerUserId = account.ownerUserId?.trim();
|
|
120
265
|
return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
|
|
@@ -143,10 +288,13 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
143
288
|
// ----- Reply state ------------------------------------------------------
|
|
144
289
|
let reasoningText = "";
|
|
145
290
|
let bufferedOutputText = "";
|
|
291
|
+
const bufferedOutputLineSet = new Set();
|
|
292
|
+
const emittedFullSegmentSet = new Set();
|
|
146
293
|
const bufferedOutputUrls = [];
|
|
147
294
|
let runDone = false;
|
|
148
295
|
let typingActive = false;
|
|
149
296
|
let terminalReplySuppressed = false;
|
|
297
|
+
let finalDeliverySeen = false;
|
|
150
298
|
const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
|
|
151
299
|
const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
|
|
152
300
|
const terminalSendScopeId = options.terminalSendScopeId ?? null;
|
|
@@ -231,14 +379,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
231
379
|
recordOutbound("thinking", messageId, thinkingText);
|
|
232
380
|
reasoningText = "";
|
|
233
381
|
};
|
|
234
|
-
const resetBufferedOutput = () => {
|
|
235
|
-
bufferedOutputText = "";
|
|
236
|
-
bufferedOutputUrls.length = 0;
|
|
237
|
-
};
|
|
238
382
|
const appendBufferedText = (value) => {
|
|
239
383
|
const trimmed = value.trim();
|
|
240
384
|
if (!trimmed)
|
|
241
385
|
return;
|
|
386
|
+
if (!trimmed.includes("\n")) {
|
|
387
|
+
if (bufferedOutputLineSet.has(trimmed))
|
|
388
|
+
return;
|
|
389
|
+
bufferedOutputLineSet.add(trimmed);
|
|
390
|
+
}
|
|
242
391
|
bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
|
|
243
392
|
};
|
|
244
393
|
const appendBufferedUrls = (urls) => {
|
|
@@ -247,13 +396,17 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
247
396
|
bufferedOutputUrls.push(url);
|
|
248
397
|
}
|
|
249
398
|
};
|
|
399
|
+
const fullSegmentKey = (text, urls) => JSON.stringify({
|
|
400
|
+
text: text.replace(/\s+/g, " ").trim(),
|
|
401
|
+
urls: urls.filter(Boolean),
|
|
402
|
+
});
|
|
250
403
|
const mergeFinalText = (text) => {
|
|
251
|
-
if (outputVisibility
|
|
404
|
+
if (outputVisibility === "minimal" || outputVisibility === "full")
|
|
252
405
|
return text;
|
|
253
406
|
return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
|
|
254
407
|
};
|
|
255
408
|
const mergeFinalUrls = (urls) => {
|
|
256
|
-
if (outputVisibility === "minimal")
|
|
409
|
+
if (outputVisibility === "minimal" || outputVisibility === "full")
|
|
257
410
|
return urls;
|
|
258
411
|
const merged = bufferedOutputUrls.slice();
|
|
259
412
|
for (const url of urls) {
|
|
@@ -369,14 +522,38 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
369
522
|
});
|
|
370
523
|
return result;
|
|
371
524
|
};
|
|
525
|
+
const emitFullSegment = async (text, urls = []) => {
|
|
526
|
+
if (outputVisibility !== "full") {
|
|
527
|
+
appendBufferedText(text);
|
|
528
|
+
appendBufferedUrls(urls);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!splitFullOutput) {
|
|
532
|
+
appendBufferedText(text);
|
|
533
|
+
appendBufferedUrls(urls);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const trimmed = text.trim();
|
|
537
|
+
if (!trimmed && urls.length === 0)
|
|
538
|
+
return;
|
|
539
|
+
const segmentKey = fullSegmentKey(trimmed, urls);
|
|
540
|
+
if (emittedFullSegmentSet.has(segmentKey))
|
|
541
|
+
return;
|
|
542
|
+
emittedFullSegmentSet.add(segmentKey);
|
|
543
|
+
const mediaFragments = await uploadMediaUrls(urls);
|
|
544
|
+
await sendStatic(trimmed, mediaFragments, [], { recordMessage: true });
|
|
545
|
+
};
|
|
546
|
+
const emitFullRuntimeText = async (label, text, urls = []) => {
|
|
547
|
+
const summary = summarizeValue(text);
|
|
548
|
+
if (!summary && urls.length === 0)
|
|
549
|
+
return;
|
|
550
|
+
await emitFullSegment(summary ? `[${label}] ${summary}` : "", urls);
|
|
551
|
+
};
|
|
372
552
|
// ----- Dispatcher -------------------------------------------------------
|
|
373
553
|
const base = runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
374
554
|
humanDelay,
|
|
375
555
|
onReplyStart: async () => {
|
|
376
556
|
emitTyping(true);
|
|
377
|
-
reasoningText = "";
|
|
378
|
-
resetBufferedOutput();
|
|
379
|
-
runDone = false;
|
|
380
557
|
},
|
|
381
558
|
deliver: async (payload, info) => {
|
|
382
559
|
if (consumeTerminalSend(info?.kind ?? "unknown"))
|
|
@@ -386,41 +563,47 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
386
563
|
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
387
564
|
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
565
|
if (isGroupTarget && richFragment) {
|
|
566
|
+
if (info?.kind === "final")
|
|
567
|
+
finalDeliverySeen = true;
|
|
389
568
|
if (info?.kind !== "final")
|
|
390
569
|
return;
|
|
391
570
|
await sendOwnerAttention(resolvePayloadText(payload), richFragment);
|
|
392
571
|
return;
|
|
393
572
|
}
|
|
394
573
|
if (isGroupTarget && info?.kind === "final" && looksLikeApprovalFallbackText(text)) {
|
|
574
|
+
finalDeliverySeen = true;
|
|
395
575
|
await sendOwnerAttention(text);
|
|
396
576
|
return;
|
|
397
577
|
}
|
|
398
578
|
if (payload.isReasoning) {
|
|
399
|
-
if (
|
|
579
|
+
if (outputVisibility !== "full")
|
|
400
580
|
return;
|
|
401
|
-
|
|
581
|
+
await emitFullSegment(text, urls);
|
|
402
582
|
const trimmed = text.trim();
|
|
403
583
|
if (trimmed)
|
|
404
584
|
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
405
585
|
return;
|
|
406
586
|
}
|
|
407
587
|
if (info?.kind === "tool") {
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
588
|
+
if (isDefaultToolResultText(text))
|
|
589
|
+
return;
|
|
590
|
+
if (outputVisibility === "full") {
|
|
591
|
+
await emitFullRuntimeText("tool result", text, urls);
|
|
411
592
|
}
|
|
412
593
|
return;
|
|
413
594
|
}
|
|
414
595
|
if (info?.kind === "block") {
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
596
|
+
if (outputVisibility === "full") {
|
|
597
|
+
await emitFullSegment(text, urls);
|
|
598
|
+
}
|
|
599
|
+
else if (outputVisibility === "minimal" || outputVisibility === "normal") {
|
|
418
600
|
appendBufferedText(text);
|
|
419
601
|
appendBufferedUrls(urls);
|
|
420
602
|
}
|
|
421
603
|
return;
|
|
422
604
|
}
|
|
423
605
|
if (info?.kind === "final") {
|
|
606
|
+
finalDeliverySeen = true;
|
|
424
607
|
if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
|
|
425
608
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
|
|
426
609
|
openclawLlmContextDebug.writeSnapshot({
|
|
@@ -455,6 +638,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
455
638
|
onError: (error, info) => {
|
|
456
639
|
const errorText = normalizeReplyErrorText(error);
|
|
457
640
|
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw ${info.kind} reply failed: ${errorText}`);
|
|
641
|
+
if (!isGroupTarget && outputVisibility === "full")
|
|
642
|
+
void emitFullRuntimeText("error", errorText);
|
|
458
643
|
if (isGroupTarget) {
|
|
459
644
|
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw group runtime failure suppressed from ClawChat clients group=${target.chatId}`);
|
|
460
645
|
return;
|
|
@@ -465,6 +650,16 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
465
650
|
if (runDone)
|
|
466
651
|
return;
|
|
467
652
|
runDone = true;
|
|
653
|
+
if (finalDeliverySeen)
|
|
654
|
+
return;
|
|
655
|
+
const fallbackText = bufferedOutputText.trim();
|
|
656
|
+
const fallbackUrls = bufferedOutputUrls.slice();
|
|
657
|
+
if (!fallbackText && fallbackUrls.length === 0)
|
|
658
|
+
return;
|
|
659
|
+
const mediaFragments = await uploadMediaUrls(fallbackUrls);
|
|
660
|
+
const result = await sendStatic(fallbackText, mediaFragments, [], { recordMessage: true });
|
|
661
|
+
if (result?.messageId)
|
|
662
|
+
recordThinkingIfLinked(result.messageId);
|
|
468
663
|
},
|
|
469
664
|
onCleanup: () => {
|
|
470
665
|
emitTyping(false);
|
|
@@ -475,7 +670,81 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
475
670
|
replyOptions: {
|
|
476
671
|
...base.replyOptions,
|
|
477
672
|
sourceReplyDeliveryMode: "automatic",
|
|
478
|
-
disableBlockStreaming:
|
|
673
|
+
disableBlockStreaming: true,
|
|
674
|
+
suppressDefaultToolProgressMessages: true,
|
|
675
|
+
allowProgressCallbacksWhenSourceDeliverySuppressed: outputVisibility === "full" ? true : undefined,
|
|
676
|
+
onReasoningStream: outputVisibility === "full"
|
|
677
|
+
? async (payload) => {
|
|
678
|
+
if (consumeTerminalSend("reasoning"))
|
|
679
|
+
return;
|
|
680
|
+
const text = resolvePayloadText(payload);
|
|
681
|
+
await emitFullRuntimeText("reasoning", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
682
|
+
const trimmed = text.trim();
|
|
683
|
+
if (trimmed)
|
|
684
|
+
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
685
|
+
}
|
|
686
|
+
: undefined,
|
|
687
|
+
onToolStart: outputVisibility === "full"
|
|
688
|
+
? async (payload) => {
|
|
689
|
+
if (consumeTerminalSend("tool-start"))
|
|
690
|
+
return;
|
|
691
|
+
await emitFullSegment(formatToolStartSummary(payload));
|
|
692
|
+
}
|
|
693
|
+
: undefined,
|
|
694
|
+
onToolResult: outputVisibility === "full"
|
|
695
|
+
? async (payload) => {
|
|
696
|
+
if (consumeTerminalSend("tool-result"))
|
|
697
|
+
return;
|
|
698
|
+
const text = resolvePayloadText(payload);
|
|
699
|
+
if (isDefaultToolResultText(text))
|
|
700
|
+
return;
|
|
701
|
+
await emitFullRuntimeText("tool result", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
702
|
+
}
|
|
703
|
+
: undefined,
|
|
704
|
+
onItemEvent: outputVisibility === "full"
|
|
705
|
+
? async (payload) => {
|
|
706
|
+
if (consumeTerminalSend("item-event"))
|
|
707
|
+
return;
|
|
708
|
+
if (isToolProgressItem(payload))
|
|
709
|
+
return;
|
|
710
|
+
await emitFullRuntimeText("progress", summarizeProgressPayload(payload));
|
|
711
|
+
}
|
|
712
|
+
: undefined,
|
|
713
|
+
onPlanUpdate: outputVisibility === "full"
|
|
714
|
+
? async (payload) => {
|
|
715
|
+
if (consumeTerminalSend("plan-update"))
|
|
716
|
+
return;
|
|
717
|
+
await emitFullRuntimeText("plan", summarizeProgressPayload(payload));
|
|
718
|
+
}
|
|
719
|
+
: undefined,
|
|
720
|
+
onCommandOutput: outputVisibility === "full"
|
|
721
|
+
? async (payload) => {
|
|
722
|
+
if (consumeTerminalSend("command-output"))
|
|
723
|
+
return;
|
|
724
|
+
await emitFullSegment(formatCommandOutputSummary(payload));
|
|
725
|
+
}
|
|
726
|
+
: undefined,
|
|
727
|
+
onPatchSummary: outputVisibility === "full"
|
|
728
|
+
? async (payload) => {
|
|
729
|
+
if (consumeTerminalSend("patch-summary"))
|
|
730
|
+
return;
|
|
731
|
+
await emitFullSegment(formatPatchSummary(payload));
|
|
732
|
+
}
|
|
733
|
+
: undefined,
|
|
734
|
+
onCompactionStart: outputVisibility === "full"
|
|
735
|
+
? async () => {
|
|
736
|
+
if (consumeTerminalSend("compaction-start"))
|
|
737
|
+
return;
|
|
738
|
+
await emitFullSegment("[compaction] started");
|
|
739
|
+
}
|
|
740
|
+
: undefined,
|
|
741
|
+
onCompactionEnd: outputVisibility === "full"
|
|
742
|
+
? async () => {
|
|
743
|
+
if (consumeTerminalSend("compaction-end"))
|
|
744
|
+
return;
|
|
745
|
+
await emitFullSegment("[compaction] finished");
|
|
746
|
+
}
|
|
747
|
+
: undefined,
|
|
479
748
|
},
|
|
480
749
|
markDispatchIdle: base.markDispatchIdle,
|
|
481
750
|
};
|
package/dist/src/runtime.js
CHANGED
|
@@ -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);
|
package/dist/src/tools-schema.js
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/skills/clawchat/SKILL.md
CHANGED
|
@@ -57,7 +57,6 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
|
|
|
57
57
|
| Reply to an existing comment | `clawchat_reply_moment_comment` with `replyToCommentId` |
|
|
58
58
|
| Delete a comment/reply | `clawchat_delete_moment_comment` with exact `momentId` and `commentId` |
|
|
59
59
|
| Nickname or bio update | `clawchat_update_account_profile` |
|
|
60
|
-
| Standalone shareable media URL | `clawchat_upload_media_file` |
|
|
61
60
|
|
|
62
61
|
## Profile And Identity Sync
|
|
63
62
|
|