@clawling/clawchat-plugin-openclaw 2026.5.12-32 → 2026.5.12-39
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 +299 -24
- 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 +13 -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 +315 -25
- 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,163 @@ 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";
|
|
263
|
+
const splitNormalBlockOutput = outputVisibility === "normal";
|
|
118
264
|
const ownerDirectTarget = () => {
|
|
119
265
|
const ownerUserId = account.ownerUserId?.trim();
|
|
120
266
|
return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
|
|
@@ -143,10 +289,13 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
143
289
|
// ----- Reply state ------------------------------------------------------
|
|
144
290
|
let reasoningText = "";
|
|
145
291
|
let bufferedOutputText = "";
|
|
292
|
+
const bufferedOutputLineSet = new Set();
|
|
293
|
+
const emittedFullSegmentSet = new Set();
|
|
146
294
|
const bufferedOutputUrls = [];
|
|
147
295
|
let runDone = false;
|
|
148
296
|
let typingActive = false;
|
|
149
297
|
let terminalReplySuppressed = false;
|
|
298
|
+
let finalDeliverySeen = false;
|
|
150
299
|
const outboundEventType = () => (replyCtx ? "message.reply" : "message.send");
|
|
151
300
|
const outboundRaw = () => ({ target, replyCtx: replyCtx ?? null });
|
|
152
301
|
const terminalSendScopeId = options.terminalSendScopeId ?? null;
|
|
@@ -231,14 +380,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
231
380
|
recordOutbound("thinking", messageId, thinkingText);
|
|
232
381
|
reasoningText = "";
|
|
233
382
|
};
|
|
234
|
-
const resetBufferedOutput = () => {
|
|
235
|
-
bufferedOutputText = "";
|
|
236
|
-
bufferedOutputUrls.length = 0;
|
|
237
|
-
};
|
|
238
383
|
const appendBufferedText = (value) => {
|
|
239
384
|
const trimmed = value.trim();
|
|
240
385
|
if (!trimmed)
|
|
241
386
|
return;
|
|
387
|
+
if (!trimmed.includes("\n")) {
|
|
388
|
+
if (bufferedOutputLineSet.has(trimmed))
|
|
389
|
+
return;
|
|
390
|
+
bufferedOutputLineSet.add(trimmed);
|
|
391
|
+
}
|
|
242
392
|
bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
|
|
243
393
|
};
|
|
244
394
|
const appendBufferedUrls = (urls) => {
|
|
@@ -247,13 +397,17 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
247
397
|
bufferedOutputUrls.push(url);
|
|
248
398
|
}
|
|
249
399
|
};
|
|
400
|
+
const fullSegmentKey = (text, urls) => JSON.stringify({
|
|
401
|
+
text: text.replace(/\s+/g, " ").trim(),
|
|
402
|
+
urls: urls.filter(Boolean),
|
|
403
|
+
});
|
|
250
404
|
const mergeFinalText = (text) => {
|
|
251
|
-
if (outputVisibility
|
|
405
|
+
if (outputVisibility === "minimal" || outputVisibility === "full")
|
|
252
406
|
return text;
|
|
253
407
|
return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
|
|
254
408
|
};
|
|
255
409
|
const mergeFinalUrls = (urls) => {
|
|
256
|
-
if (outputVisibility === "minimal")
|
|
410
|
+
if (outputVisibility === "minimal" || outputVisibility === "full")
|
|
257
411
|
return urls;
|
|
258
412
|
const merged = bufferedOutputUrls.slice();
|
|
259
413
|
for (const url of urls) {
|
|
@@ -369,14 +523,38 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
369
523
|
});
|
|
370
524
|
return result;
|
|
371
525
|
};
|
|
526
|
+
const emitFullSegment = async (text, urls = []) => {
|
|
527
|
+
if (outputVisibility !== "full" && !splitNormalBlockOutput) {
|
|
528
|
+
appendBufferedText(text);
|
|
529
|
+
appendBufferedUrls(urls);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (!splitFullOutput && !splitNormalBlockOutput) {
|
|
533
|
+
appendBufferedText(text);
|
|
534
|
+
appendBufferedUrls(urls);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const trimmed = text.trim();
|
|
538
|
+
if (!trimmed && urls.length === 0)
|
|
539
|
+
return;
|
|
540
|
+
const segmentKey = fullSegmentKey(trimmed, urls);
|
|
541
|
+
if (emittedFullSegmentSet.has(segmentKey))
|
|
542
|
+
return;
|
|
543
|
+
emittedFullSegmentSet.add(segmentKey);
|
|
544
|
+
const mediaFragments = await uploadMediaUrls(urls);
|
|
545
|
+
await sendStatic(trimmed, mediaFragments, [], { recordMessage: true });
|
|
546
|
+
};
|
|
547
|
+
const emitFullRuntimeText = async (label, text, urls = []) => {
|
|
548
|
+
const summary = summarizeValue(text);
|
|
549
|
+
if (!summary && urls.length === 0)
|
|
550
|
+
return;
|
|
551
|
+
await emitFullSegment(summary ? `[${label}] ${summary}` : "", urls);
|
|
552
|
+
};
|
|
372
553
|
// ----- Dispatcher -------------------------------------------------------
|
|
373
554
|
const base = runtime.channel.reply.createReplyDispatcherWithTyping({
|
|
374
555
|
humanDelay,
|
|
375
556
|
onReplyStart: async () => {
|
|
376
557
|
emitTyping(true);
|
|
377
|
-
reasoningText = "";
|
|
378
|
-
resetBufferedOutput();
|
|
379
|
-
runDone = false;
|
|
380
558
|
},
|
|
381
559
|
deliver: async (payload, info) => {
|
|
382
560
|
if (consumeTerminalSend(info?.kind ?? "unknown"))
|
|
@@ -386,41 +564,50 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
386
564
|
const urls = resolveOutboundMediaUrls(payload).filter(Boolean);
|
|
387
565
|
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
566
|
if (isGroupTarget && richFragment) {
|
|
567
|
+
if (info?.kind === "final")
|
|
568
|
+
finalDeliverySeen = true;
|
|
389
569
|
if (info?.kind !== "final")
|
|
390
570
|
return;
|
|
391
571
|
await sendOwnerAttention(resolvePayloadText(payload), richFragment);
|
|
392
572
|
return;
|
|
393
573
|
}
|
|
394
574
|
if (isGroupTarget && info?.kind === "final" && looksLikeApprovalFallbackText(text)) {
|
|
575
|
+
finalDeliverySeen = true;
|
|
395
576
|
await sendOwnerAttention(text);
|
|
396
577
|
return;
|
|
397
578
|
}
|
|
398
579
|
if (payload.isReasoning) {
|
|
399
|
-
if (
|
|
580
|
+
if (outputVisibility !== "full")
|
|
400
581
|
return;
|
|
401
|
-
|
|
582
|
+
await emitFullSegment(text, urls);
|
|
402
583
|
const trimmed = text.trim();
|
|
403
584
|
if (trimmed)
|
|
404
585
|
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
405
586
|
return;
|
|
406
587
|
}
|
|
407
588
|
if (info?.kind === "tool") {
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
589
|
+
if (isDefaultToolResultText(text))
|
|
590
|
+
return;
|
|
591
|
+
if (outputVisibility === "full") {
|
|
592
|
+
await emitFullRuntimeText("tool result", text, urls);
|
|
411
593
|
}
|
|
412
594
|
return;
|
|
413
595
|
}
|
|
414
596
|
if (info?.kind === "block") {
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
597
|
+
if (outputVisibility === "full") {
|
|
598
|
+
await emitFullSegment(text, urls);
|
|
599
|
+
}
|
|
600
|
+
else if (splitNormalBlockOutput) {
|
|
601
|
+
await emitFullSegment(text, urls);
|
|
602
|
+
}
|
|
603
|
+
else if (outputVisibility === "minimal" || outputVisibility === "normal") {
|
|
418
604
|
appendBufferedText(text);
|
|
419
605
|
appendBufferedUrls(urls);
|
|
420
606
|
}
|
|
421
607
|
return;
|
|
422
608
|
}
|
|
423
609
|
if (info?.kind === "final") {
|
|
610
|
+
finalDeliverySeen = true;
|
|
424
611
|
if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
|
|
425
612
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
|
|
426
613
|
openclawLlmContextDebug.writeSnapshot({
|
|
@@ -444,6 +631,12 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
444
631
|
}
|
|
445
632
|
const finalText = richFragment && account.richInteractions ? mergeFinalText("") : mergeFinalText(text);
|
|
446
633
|
const finalUrls = mergeFinalUrls(urls);
|
|
634
|
+
if (isClawChatNoopResponseText(finalText) &&
|
|
635
|
+
!richFragment &&
|
|
636
|
+
finalUrls.length === 0) {
|
|
637
|
+
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
447
640
|
const mediaFragments = await uploadMediaUrls(finalUrls);
|
|
448
641
|
const result = await sendStatic(finalText, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
|
|
449
642
|
if (result?.messageId)
|
|
@@ -455,16 +648,24 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
455
648
|
onError: (error, info) => {
|
|
456
649
|
const errorText = normalizeReplyErrorText(error);
|
|
457
650
|
log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw ${info.kind} reply failed: ${errorText}`);
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
651
|
+
if (outputVisibility === "full")
|
|
652
|
+
void emitFullRuntimeText("error", errorText);
|
|
462
653
|
},
|
|
463
654
|
onIdle: async () => {
|
|
464
655
|
emitTyping(false);
|
|
465
656
|
if (runDone)
|
|
466
657
|
return;
|
|
467
658
|
runDone = true;
|
|
659
|
+
if (finalDeliverySeen)
|
|
660
|
+
return;
|
|
661
|
+
const fallbackText = bufferedOutputText.trim();
|
|
662
|
+
const fallbackUrls = bufferedOutputUrls.slice();
|
|
663
|
+
if (!fallbackText && fallbackUrls.length === 0)
|
|
664
|
+
return;
|
|
665
|
+
const mediaFragments = await uploadMediaUrls(fallbackUrls);
|
|
666
|
+
const result = await sendStatic(fallbackText, mediaFragments, [], { recordMessage: true });
|
|
667
|
+
if (result?.messageId)
|
|
668
|
+
recordThinkingIfLinked(result.messageId);
|
|
468
669
|
},
|
|
469
670
|
onCleanup: () => {
|
|
470
671
|
emitTyping(false);
|
|
@@ -475,7 +676,81 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
475
676
|
replyOptions: {
|
|
476
677
|
...base.replyOptions,
|
|
477
678
|
sourceReplyDeliveryMode: "automatic",
|
|
478
|
-
disableBlockStreaming:
|
|
679
|
+
disableBlockStreaming: !splitNormalBlockOutput,
|
|
680
|
+
suppressDefaultToolProgressMessages: true,
|
|
681
|
+
allowProgressCallbacksWhenSourceDeliverySuppressed: splitFullOutput ? true : undefined,
|
|
682
|
+
onReasoningStream: splitFullOutput
|
|
683
|
+
? async (payload) => {
|
|
684
|
+
if (consumeTerminalSend("reasoning"))
|
|
685
|
+
return;
|
|
686
|
+
const text = resolvePayloadText(payload);
|
|
687
|
+
await emitFullRuntimeText("reasoning", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
688
|
+
const trimmed = text.trim();
|
|
689
|
+
if (trimmed)
|
|
690
|
+
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
691
|
+
}
|
|
692
|
+
: undefined,
|
|
693
|
+
onToolStart: splitFullOutput
|
|
694
|
+
? async (payload) => {
|
|
695
|
+
if (consumeTerminalSend("tool-start"))
|
|
696
|
+
return;
|
|
697
|
+
await emitFullSegment(formatToolStartSummary(payload));
|
|
698
|
+
}
|
|
699
|
+
: undefined,
|
|
700
|
+
onToolResult: splitFullOutput
|
|
701
|
+
? async (payload) => {
|
|
702
|
+
if (consumeTerminalSend("tool-result"))
|
|
703
|
+
return;
|
|
704
|
+
const text = resolvePayloadText(payload);
|
|
705
|
+
if (isDefaultToolResultText(text))
|
|
706
|
+
return;
|
|
707
|
+
await emitFullRuntimeText("tool result", text, resolveOutboundMediaUrls(payload).filter(Boolean));
|
|
708
|
+
}
|
|
709
|
+
: undefined,
|
|
710
|
+
onItemEvent: splitFullOutput
|
|
711
|
+
? async (payload) => {
|
|
712
|
+
if (consumeTerminalSend("item-event"))
|
|
713
|
+
return;
|
|
714
|
+
if (isToolProgressItem(payload))
|
|
715
|
+
return;
|
|
716
|
+
await emitFullRuntimeText("progress", summarizeProgressPayload(payload));
|
|
717
|
+
}
|
|
718
|
+
: undefined,
|
|
719
|
+
onPlanUpdate: splitFullOutput
|
|
720
|
+
? async (payload) => {
|
|
721
|
+
if (consumeTerminalSend("plan-update"))
|
|
722
|
+
return;
|
|
723
|
+
await emitFullRuntimeText("plan", summarizeProgressPayload(payload));
|
|
724
|
+
}
|
|
725
|
+
: undefined,
|
|
726
|
+
onCommandOutput: splitFullOutput
|
|
727
|
+
? async (payload) => {
|
|
728
|
+
if (consumeTerminalSend("command-output"))
|
|
729
|
+
return;
|
|
730
|
+
await emitFullSegment(formatCommandOutputSummary(payload));
|
|
731
|
+
}
|
|
732
|
+
: undefined,
|
|
733
|
+
onPatchSummary: splitFullOutput
|
|
734
|
+
? async (payload) => {
|
|
735
|
+
if (consumeTerminalSend("patch-summary"))
|
|
736
|
+
return;
|
|
737
|
+
await emitFullSegment(formatPatchSummary(payload));
|
|
738
|
+
}
|
|
739
|
+
: undefined,
|
|
740
|
+
onCompactionStart: splitFullOutput
|
|
741
|
+
? async () => {
|
|
742
|
+
if (consumeTerminalSend("compaction-start"))
|
|
743
|
+
return;
|
|
744
|
+
await emitFullSegment("[compaction] started");
|
|
745
|
+
}
|
|
746
|
+
: undefined,
|
|
747
|
+
onCompactionEnd: splitFullOutput
|
|
748
|
+
? async () => {
|
|
749
|
+
if (consumeTerminalSend("compaction-end"))
|
|
750
|
+
return;
|
|
751
|
+
await emitFullSegment("[compaction] finished");
|
|
752
|
+
}
|
|
753
|
+
: undefined,
|
|
479
754
|
},
|
|
480
755
|
markDispatchIdle: base.markDispatchIdle,
|
|
481
756
|
};
|
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