@clawling/clawchat-plugin-openclaw 2026.9.26-3 → 2026.10.7-2
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/config.js +3 -0
- package/dist/src/framework-error-copy.js +172 -0
- package/dist/src/liveware-sample.js +106 -19
- package/dist/src/mention-autolink.js +155 -0
- package/dist/src/no-reply.js +7 -6
- package/dist/src/outbound.js +28 -7
- package/dist/src/permission-result.js +116 -27
- package/dist/src/profile-prompt.js +17 -2
- package/dist/src/reply-dispatcher.js +145 -31
- package/dist/src/reply-stream.js +208 -0
- package/dist/src/runtime.js +135 -2
- package/dist/src/skill-update.js +1 -1
- package/dist/src/tools-schema.js +1 -1
- package/dist/src/tools.js +105 -12
- package/openclaw.plugin.json +4 -0
- package/package.json +3 -8
- package/skills/clawchat-core/SKILL.md +12 -3
- package/skills/clawchat-liveware-sample/SKILL.md +3 -2
- package/skills/clawchat-orchestration/SKILL.md +30 -1
- package/skills/clawchat-set-greeting/SKILL.md +5 -4
- package/skills/manifest.json +31 -31
- package/src/config.ts +7 -0
- package/src/framework-error-copy.ts +185 -0
- package/src/liveware-sample.ts +106 -19
- package/src/mention-autolink.ts +200 -0
- package/src/no-reply.ts +7 -6
- package/src/outbound.ts +58 -7
- package/src/permission-result.ts +134 -30
- package/src/profile-prompt.ts +21 -2
- package/src/reply-dispatcher.ts +163 -28
- package/src/reply-stream.ts +244 -0
- package/src/runtime.ts +136 -3
- package/src/skill-update.ts +1 -1
- package/src/tools-schema.ts +1 -1
- package/src/tools.ts +112 -10
package/src/tools.ts
CHANGED
|
@@ -148,6 +148,17 @@ function extractRequestId(err: ClawlingApiError): string | undefined {
|
|
|
148
148
|
return undefined;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* What the agent must do with the later `permission_result` receipt
|
|
153
|
+
* (agent-protocol §2.8): on approval the SERVER executes the gated call, so a
|
|
154
|
+
* second call would duplicate it or raise a fresh card. Only `approved_retry`
|
|
155
|
+
* (reads, which are never replayed) asks for exactly one more call.
|
|
156
|
+
*/
|
|
157
|
+
const PERMISSION_RECEIPT_RULE =
|
|
158
|
+
"When the result arrives: outcome \"approved\" (or \"auto_allowed\") means the server has already done it for you — do not call this tool again; " +
|
|
159
|
+
"only outcome \"approved_retry\" means call this same tool once more with the same arguments. " +
|
|
160
|
+
"Anything the server produced (a new id, a code, read data) comes in that message's Result line.";
|
|
161
|
+
|
|
151
162
|
function permissionGateResult(err: ClawlingApiError) {
|
|
152
163
|
const code = (err.meta as Record<string, unknown> | undefined)?.code;
|
|
153
164
|
const requestId = extractRequestId(err);
|
|
@@ -158,7 +169,8 @@ function permissionGateResult(err: ClawlingApiError) {
|
|
|
158
169
|
"This operation requires the owner's approval and has been submitted for review" +
|
|
159
170
|
(requestId ? ` (request_id=${requestId})` : "") +
|
|
160
171
|
". It has NOT failed. Do not retry — the result will arrive later as a normal " +
|
|
161
|
-
"chat message; wait for it instead of calling this tool again."
|
|
172
|
+
"chat message; wait for it instead of calling this tool again. " +
|
|
173
|
+
PERMISSION_RECEIPT_RULE;
|
|
162
174
|
status = "pending";
|
|
163
175
|
} else {
|
|
164
176
|
message =
|
|
@@ -257,6 +269,50 @@ function inferMimeFromPath(filePath: string): string {
|
|
|
257
269
|
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
258
270
|
}
|
|
259
271
|
|
|
272
|
+
type MomentImageEntry = { kind: "url"; url: string } | { kind: "local"; path: string };
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Classify `clawchat_create_moment` image entries: http(s) URLs pass through;
|
|
276
|
+
* an existing absolute local regular file (≤ 20MB) is marked for upload; any
|
|
277
|
+
* other value is a validation error naming the offending index.
|
|
278
|
+
*/
|
|
279
|
+
function planMomentImages(
|
|
280
|
+
images: unknown[],
|
|
281
|
+
): { ok: true; entries: MomentImageEntry[] } | { ok: false; message: string } {
|
|
282
|
+
const entries: MomentImageEntry[] = [];
|
|
283
|
+
for (const [i, raw] of images.entries()) {
|
|
284
|
+
const where = `clawchat-plugin-openclaw: images[${i}]`;
|
|
285
|
+
const value = typeof raw === "string" ? raw.trim() : "";
|
|
286
|
+
if (!value) {
|
|
287
|
+
return { ok: false, message: `${where} must be an http(s) image URL or an absolute local image path` };
|
|
288
|
+
}
|
|
289
|
+
if (/^https?:\/\/\S+$/i.test(value)) {
|
|
290
|
+
entries.push({ kind: "url", url: value });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (!path.isAbsolute(value)) {
|
|
294
|
+
return {
|
|
295
|
+
ok: false,
|
|
296
|
+
message: `${where} (${JSON.stringify(value)}) is neither an http(s) URL nor an absolute local file path; no moment was created`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
let stat: fs.Stats;
|
|
300
|
+
try {
|
|
301
|
+
stat = fs.statSync(value);
|
|
302
|
+
} catch {
|
|
303
|
+
return { ok: false, message: `${where}: local file ${value} does not exist; no moment was created` };
|
|
304
|
+
}
|
|
305
|
+
if (!stat.isFile()) {
|
|
306
|
+
return { ok: false, message: `${where}: ${value} is not a regular file; no moment was created` };
|
|
307
|
+
}
|
|
308
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
309
|
+
return { ok: false, message: `${where}: ${value} is too large (${stat.size} bytes; max 20MB)` };
|
|
310
|
+
}
|
|
311
|
+
entries.push({ kind: "local", path: value });
|
|
312
|
+
}
|
|
313
|
+
return { ok: true, entries };
|
|
314
|
+
}
|
|
315
|
+
|
|
260
316
|
function validateMentionMessageParams(params: unknown):
|
|
261
317
|
| { ok: true; params: ClawchatMentionMessageParams }
|
|
262
318
|
| { ok: false; message: string } {
|
|
@@ -442,6 +498,7 @@ export function registerOpenclawClawlingTools(
|
|
|
442
498
|
ok: true,
|
|
443
499
|
client: createOpenclawClawlingApiClient({
|
|
444
500
|
baseUrl: acct.baseUrl,
|
|
501
|
+
mediaBaseUrl: acct.mediaBaseUrl,
|
|
445
502
|
token: acct.token,
|
|
446
503
|
userId: acct.userId,
|
|
447
504
|
}),
|
|
@@ -490,7 +547,19 @@ export function registerOpenclawClawlingTools(
|
|
|
490
547
|
? (rawData as Record<string, unknown>)
|
|
491
548
|
: {};
|
|
492
549
|
const outcome = mapGateOutcome(code, gateData);
|
|
493
|
-
if (outcome !== null)
|
|
550
|
+
if (outcome !== null) {
|
|
551
|
+
// `next_step` is added here, not in mapGateOutcome: that mapping
|
|
552
|
+
// is pinned byte-for-byte against the Hermes parity fixtures.
|
|
553
|
+
return jsonResponse(
|
|
554
|
+
outcome.status === "pending_owner_approval"
|
|
555
|
+
? {
|
|
556
|
+
...outcome,
|
|
557
|
+
next_step:
|
|
558
|
+
"Waiting for the owner's approval; do not call this tool again now. " + PERMISSION_RECEIPT_RULE,
|
|
559
|
+
}
|
|
560
|
+
: outcome,
|
|
561
|
+
);
|
|
562
|
+
}
|
|
494
563
|
}
|
|
495
564
|
return apiError(err);
|
|
496
565
|
}
|
|
@@ -1413,7 +1482,7 @@ export function registerOpenclawClawlingTools(
|
|
|
1413
1482
|
description: toolDescription(
|
|
1414
1483
|
"Create a new ClawChat moment/dynamic for the configured ClawChat account. " +
|
|
1415
1484
|
"TRIGGER - invoke when the user asks to publish, post, or send a ClawChat moment/dynamic, such as \"post a ClawChat moment saying ...\", \"publish a dynamic ...\", or \"send a friend moment ...\". " +
|
|
1416
|
-
"At least one of text or images must be present.
|
|
1485
|
+
"At least one of text or images must be present. Each images entry is either an http(s) image URL (used as-is) or an absolute local image path (max 20MB) — for a local path this tool uploads it to ClawChat media itself and uses the hosted URL, so no separate upload step is needed. Any other value (relative path, file:// or data: URI, missing file) is rejected and no moment is created.",
|
|
1417
1486
|
),
|
|
1418
1487
|
parameters: ClawchatCreateMomentSchema,
|
|
1419
1488
|
async execute(_callId, params) {
|
|
@@ -1424,12 +1493,34 @@ export function registerOpenclawClawlingTools(
|
|
|
1424
1493
|
if (!text && (!images || images.length === 0)) {
|
|
1425
1494
|
return validationError("clawchat-plugin-openclaw: at least one of text or images is required");
|
|
1426
1495
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1496
|
+
// Validate every entry before touching the network, so a bad value
|
|
1497
|
+
// never leaves a half-uploaded batch or a moment with a broken image.
|
|
1498
|
+
const plan = images !== undefined ? planMomentImages(images) : { ok: true as const, entries: [] };
|
|
1499
|
+
if (!plan.ok) return validationError(plan.message);
|
|
1500
|
+
return await withGatedClient(accountId, async (c) => {
|
|
1501
|
+
const resolved: string[] = [];
|
|
1502
|
+
for (const entry of plan.entries) {
|
|
1503
|
+
if (entry.kind === "url") {
|
|
1504
|
+
resolved.push(entry.url);
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
const uploaded = await c.uploadMedia({
|
|
1508
|
+
buffer: fs.readFileSync(entry.path),
|
|
1509
|
+
filename: path.basename(entry.path),
|
|
1510
|
+
mime: inferMimeFromPath(entry.path),
|
|
1511
|
+
});
|
|
1512
|
+
if (uploaded.kind !== "image") {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
`clawchat-plugin-openclaw: ${entry.path} is not an image (media service classified it as ${uploaded.kind}); no moment was created`,
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
resolved.push(uploaded.url);
|
|
1518
|
+
}
|
|
1519
|
+
return await c.createMoment({
|
|
1429
1520
|
...(text !== undefined ? { text } : {}),
|
|
1430
|
-
...(images !== undefined ? { images } : {}),
|
|
1431
|
-
})
|
|
1432
|
-
);
|
|
1521
|
+
...(images !== undefined ? { images: resolved } : {}),
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1433
1524
|
});
|
|
1434
1525
|
},
|
|
1435
1526
|
};
|
|
@@ -1867,11 +1958,18 @@ export function registerOpenclawClawlingTools(
|
|
|
1867
1958
|
name: "clawchat_orchestrate_set_agent_behavior",
|
|
1868
1959
|
label: "Rewrite A Sibling's System Prompt",
|
|
1869
1960
|
description: toolDescription(
|
|
1961
|
+
"Stage/room speaking rules go in the group description, not here; for a stage, append the stage sentence below to off-desktop members (clawchat-orchestration skill). " +
|
|
1870
1962
|
"Replace the WHOLE system prompt (behavior) of one of the owner's other agents. " +
|
|
1871
1963
|
"TRIGGER — invoke when the owner asks to change how another agent behaves, talks, or what it is for. " +
|
|
1872
1964
|
"This REPLACES the entire field — call clawchat_orchestrate_get_agent first, edit the text you got back, and send the full new value. " +
|
|
1873
1965
|
"Sending a fragment deletes everything else and the owner cannot recover it. Max 3000 runes. " +
|
|
1874
|
-
"Nickname and bio cannot be changed here."
|
|
1966
|
+
"Nickname and bio cannot be changed here. " +
|
|
1967
|
+
"Behavior follows an agent into every room, so one room's rules never go here — they go in that group's description (clawchat_orchestrate_set_group_prompt). " +
|
|
1968
|
+
"A stage (a group whose description has its agents chime in freely) adds only one line here, the same for every room: " +
|
|
1969
|
+
"give each member that the ClawChat desktop app does not run on the owner's computer — Hermes and OpenClaw plugin agents among them — this sentence, verbatim, " +
|
|
1970
|
+
"appended without deleting anything and without naming the room: " +
|
|
1971
|
+
"\"In a group whose description makes it a stage, you are one of the players: pick up the other characters' lines without waiting to be called.\" " +
|
|
1972
|
+
"Older plugin versions rank the group description below their own reply rules; this line is what lets them speak on a stage.",
|
|
1875
1973
|
),
|
|
1876
1974
|
parameters: ClawchatOrchSetAgentBehaviorSchema,
|
|
1877
1975
|
async execute(_callId, params) {
|
|
@@ -1940,11 +2038,15 @@ export function registerOpenclawClawlingTools(
|
|
|
1940
2038
|
name: "clawchat_orchestrate_set_group_prompt",
|
|
1941
2039
|
label: "Rewrite A Group's System Prompt",
|
|
1942
2040
|
description: toolDescription(
|
|
2041
|
+
"Stage/room speaking rules go here, in the group description; for a stage, also append the stage sentence to off-desktop members' behavior (clawchat-orchestration skill). " +
|
|
1943
2042
|
"Replace the WHOLE system prompt (description) of a group the owner administers. " +
|
|
1944
2043
|
"TRIGGER — invoke when the owner asks to change how a group behaves as a whole. " +
|
|
1945
2044
|
"This REPLACES the entire field — call clawchat_orchestrate_get_group first and send the full new value. " +
|
|
1946
2045
|
"Sending a fragment deletes everything else and the owner cannot recover it. Max 3000 runes. " +
|
|
1947
|
-
"The group title cannot be changed here."
|
|
2046
|
+
"The group title cannot be changed here. " +
|
|
2047
|
+
"A room where agents should chime in freely, a stage, says so in its description, and its own rules go nowhere else: behavior follows an agent into every room. " +
|
|
2048
|
+
"A stage also needs one room-independent line in the behavior of each member the ClawChat desktop app does not run on the owner's computer, appended verbatim with clawchat_orchestrate_set_agent_behavior: " +
|
|
2049
|
+
"\"In a group whose description makes it a stage, you are one of the players: pick up the other characters' lines without waiting to be called.\"",
|
|
1948
2050
|
),
|
|
1949
2051
|
parameters: ClawchatOrchSetGroupPromptSchema,
|
|
1950
2052
|
async execute(_callId, params) {
|