@clawling/clawchat-plugin-openclaw 2026.5.12-28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +64 -0
- package/README.md +227 -0
- package/dist/index.js +20 -0
- package/dist/setup-entry.js +3 -0
- package/dist/src/api-client.js +263 -0
- package/dist/src/api-types.js +17 -0
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/buffered-stream.js +177 -0
- package/dist/src/channel.js +66 -0
- package/dist/src/channel.setup.js +119 -0
- package/dist/src/clawchat-memory.js +403 -0
- package/dist/src/clawchat-metadata.js +310 -0
- package/dist/src/client.js +35 -0
- package/dist/src/commands.js +35 -0
- package/dist/src/config.js +274 -0
- package/dist/src/group-message-coalescer.js +119 -0
- package/dist/src/inbound.js +170 -0
- package/dist/src/llm-context-debug.js +86 -0
- package/dist/src/login.runtime.js +204 -0
- package/dist/src/media-runtime.js +85 -0
- package/dist/src/message-mapper.js +146 -0
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +628 -0
- package/dist/src/plugin-prompts.js +89 -0
- package/dist/src/profile-prompt.js +269 -0
- package/dist/src/profile-sync.js +110 -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 +33 -0
- package/dist/src/reply-dispatcher.js +422 -0
- package/dist/src/runtime.js +1254 -0
- package/dist/src/storage.js +525 -0
- package/dist/src/streaming.js +65 -0
- package/dist/src/terminal-send.js +36 -0
- package/dist/src/tools-schema.js +208 -0
- package/dist/src/tools.js +920 -0
- package/dist/src/ws-alignment.js +178 -0
- package/dist/src/ws-client.js +588 -0
- package/dist/src/ws-log.js +19 -0
- package/index.ts +24 -0
- package/openclaw.plugin.json +169 -0
- package/package.json +80 -0
- package/prompts/default-group-bio.md +19 -0
- package/prompts/default-owner-behavior.md +27 -0
- package/prompts/platform.md +13 -0
- package/setup-entry.ts +4 -0
- package/skills/clawchat/SKILL.md +91 -0
- package/src/api-client.test.ts +827 -0
- package/src/api-client.ts +414 -0
- package/src/api-types.ts +146 -0
- package/src/channel.outbound.test.ts +433 -0
- package/src/channel.setup.ts +145 -0
- package/src/channel.test.ts +262 -0
- package/src/channel.ts +81 -0
- package/src/clawchat-memory.test.ts +480 -0
- package/src/clawchat-memory.ts +533 -0
- package/src/clawchat-metadata.test.ts +477 -0
- package/src/clawchat-metadata.ts +429 -0
- package/src/client.test.ts +169 -0
- package/src/client.ts +56 -0
- package/src/commands.test.ts +39 -0
- package/src/commands.ts +41 -0
- package/src/config.test.ts +344 -0
- package/src/config.ts +404 -0
- package/src/group-message-coalescer.test.ts +237 -0
- package/src/group-message-coalescer.ts +171 -0
- package/src/inbound.test.ts +508 -0
- package/src/inbound.ts +278 -0
- package/src/llm-context-debug.test.ts +55 -0
- package/src/llm-context-debug.ts +139 -0
- package/src/login.runtime.test.ts +737 -0
- package/src/login.runtime.ts +277 -0
- package/src/manifest.test.ts +352 -0
- package/src/media-runtime.test.ts +207 -0
- package/src/media-runtime.ts +152 -0
- package/src/message-mapper.test.ts +201 -0
- package/src/message-mapper.ts +174 -0
- package/src/mock-transport.test.ts +35 -0
- package/src/mock-transport.ts +38 -0
- package/src/outbound.test.ts +1269 -0
- package/src/outbound.ts +803 -0
- package/src/plugin-entry.test.ts +38 -0
- package/src/plugin-prompts.test.ts +94 -0
- package/src/plugin-prompts.ts +107 -0
- package/src/profile-prompt.test.ts +274 -0
- package/src/profile-prompt.ts +351 -0
- package/src/profile-sync.test.ts +539 -0
- package/src/profile-sync.ts +191 -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.test.ts +39 -0
- package/src/protocol.ts +42 -0
- package/src/reply-dispatcher.test.ts +1324 -0
- package/src/reply-dispatcher.ts +555 -0
- package/src/runtime.test.ts +4719 -0
- package/src/runtime.ts +1493 -0
- package/src/scripts.test.ts +85 -0
- package/src/storage.test.ts +560 -0
- package/src/storage.ts +807 -0
- package/src/terminal-send.test.ts +81 -0
- package/src/terminal-send.ts +56 -0
- package/src/tools-schema.ts +337 -0
- package/src/tools.test.ts +933 -0
- package/src/tools.ts +1185 -0
- package/src/ws-alignment.test.ts +103 -0
- package/src/ws-alignment.ts +275 -0
- package/src/ws-client.test.ts +1217 -0
- package/src/ws-client.ts +662 -0
- package/src/ws-log.test.ts +32 -0
- package/src/ws-log.ts +31 -0
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
4
|
+
import { ClawlingApiError, } from "./api-types.js";
|
|
5
|
+
import { resolveOpenclawClawlingAccount } from "./config.js";
|
|
6
|
+
import { clawChatDbPathForStateDir, getClawChatStore, } from "./storage.js";
|
|
7
|
+
import { sendOpenclawClawlingMentionMessage } from "./outbound.js";
|
|
8
|
+
import { getOpenclawClawlingClient, } from "./runtime.js";
|
|
9
|
+
import { markTerminalClawChatSend } from "./terminal-send.js";
|
|
10
|
+
import { editClawChatMemoryBody, readClawChatMemoryFile, resolveClawChatMemoryPath, searchClawChatMemory, writeClawChatMemoryBody, } from "./clawchat-memory.js";
|
|
11
|
+
import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, pushMetadata, updateMetadata, } from "./clawchat-metadata.js";
|
|
12
|
+
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatSearchUsersSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatUploadMediaFileSchema, } from "./tools-schema.js";
|
|
13
|
+
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
14
|
+
function jsonResponse(data) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
17
|
+
details: data,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function configError(message) {
|
|
21
|
+
return jsonResponse({ error: "config", message });
|
|
22
|
+
}
|
|
23
|
+
function apiError(err) {
|
|
24
|
+
return jsonResponse({
|
|
25
|
+
error: err.kind,
|
|
26
|
+
message: err.message,
|
|
27
|
+
...(err.meta ? { meta: err.meta } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function validationError(message) {
|
|
31
|
+
return jsonResponse({ error: "validation", message });
|
|
32
|
+
}
|
|
33
|
+
function genericError(err) {
|
|
34
|
+
return jsonResponse({
|
|
35
|
+
error: "unknown",
|
|
36
|
+
message: err instanceof Error ? err.message : String(err),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const MIME_BY_EXT = {
|
|
40
|
+
".png": "image/png",
|
|
41
|
+
".jpg": "image/jpeg",
|
|
42
|
+
".jpeg": "image/jpeg",
|
|
43
|
+
".gif": "image/gif",
|
|
44
|
+
".webp": "image/webp",
|
|
45
|
+
".bmp": "image/bmp",
|
|
46
|
+
".svg": "image/svg+xml",
|
|
47
|
+
".pdf": "application/pdf",
|
|
48
|
+
".txt": "text/plain",
|
|
49
|
+
".md": "text/markdown",
|
|
50
|
+
".json": "application/json",
|
|
51
|
+
".csv": "text/csv",
|
|
52
|
+
".zip": "application/zip",
|
|
53
|
+
".mp3": "audio/mpeg",
|
|
54
|
+
".m4a": "audio/mp4",
|
|
55
|
+
".wav": "audio/wav",
|
|
56
|
+
".ogg": "audio/ogg",
|
|
57
|
+
".mp4": "video/mp4",
|
|
58
|
+
".mov": "video/quicktime",
|
|
59
|
+
".webm": "video/webm",
|
|
60
|
+
};
|
|
61
|
+
const DIRECT_TOOL_GUARD = "Use this registered ClawChat plugin tool directly. Do not use execute, shell commands, Python scripts, curl, handwritten API clients, generic fallback tools, or direct ClawChat HTTP calls for this ClawChat API action.";
|
|
62
|
+
const MEMORY_READ_DEFAULT_LIMIT = 20_000;
|
|
63
|
+
const MEMORY_READ_MAX_LIMIT = 100_000;
|
|
64
|
+
function toolDescription(...parts) {
|
|
65
|
+
return `${parts.join("")} ${DIRECT_TOOL_GUARD}`;
|
|
66
|
+
}
|
|
67
|
+
function memoryToolDescription(...parts) {
|
|
68
|
+
return parts.join("");
|
|
69
|
+
}
|
|
70
|
+
function inferMimeFromPath(filePath) {
|
|
71
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
72
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
73
|
+
}
|
|
74
|
+
function validateMentionMessageParams(params) {
|
|
75
|
+
if (!params || typeof params !== "object") {
|
|
76
|
+
return { ok: false, message: "clawchat-plugin-openclaw: params are required" };
|
|
77
|
+
}
|
|
78
|
+
const p = params;
|
|
79
|
+
if (typeof p.chatId !== "string" || !p.chatId.trim()) {
|
|
80
|
+
return { ok: false, message: "clawchat-plugin-openclaw: chatId is required" };
|
|
81
|
+
}
|
|
82
|
+
if (p.chatType !== undefined && p.chatType !== "group" && p.chatType !== "direct") {
|
|
83
|
+
return { ok: false, message: "clawchat-plugin-openclaw: chatType must be group or direct" };
|
|
84
|
+
}
|
|
85
|
+
if (p.text !== undefined && typeof p.text !== "string") {
|
|
86
|
+
return { ok: false, message: "clawchat-plugin-openclaw: text must be a string" };
|
|
87
|
+
}
|
|
88
|
+
if (!Array.isArray(p.mentions) || p.mentions.length === 0) {
|
|
89
|
+
return { ok: false, message: "clawchat-plugin-openclaw: at least one mention is required" };
|
|
90
|
+
}
|
|
91
|
+
for (let i = 0; i < p.mentions.length; i += 1) {
|
|
92
|
+
const mention = p.mentions[i];
|
|
93
|
+
if (!mention || typeof mention !== "object") {
|
|
94
|
+
return { ok: false, message: `clawchat-plugin-openclaw: mentions[${i}] must be an object` };
|
|
95
|
+
}
|
|
96
|
+
if (typeof mention.userId !== "string" || !mention.userId.trim()) {
|
|
97
|
+
return { ok: false, message: `clawchat-plugin-openclaw: mentions[${i}].userId is required` };
|
|
98
|
+
}
|
|
99
|
+
if (mention.display !== undefined && typeof mention.display !== "string") {
|
|
100
|
+
return { ok: false, message: `clawchat-plugin-openclaw: mentions[${i}].display must be a string` };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (p.replyToMessageId !== undefined) {
|
|
104
|
+
if (typeof p.replyToMessageId !== "string") {
|
|
105
|
+
return { ok: false, message: "clawchat-plugin-openclaw: replyToMessageId must be a string" };
|
|
106
|
+
}
|
|
107
|
+
const replyToMessageId = p.replyToMessageId.trim();
|
|
108
|
+
if (!replyToMessageId) {
|
|
109
|
+
return { ok: false, message: "clawchat-plugin-openclaw: replyToMessageId must be non-empty" };
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
params: {
|
|
114
|
+
...p,
|
|
115
|
+
replyToMessageId,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { ok: true, params: p };
|
|
120
|
+
}
|
|
121
|
+
export function registerOpenclawClawlingTools(api, options = {}) {
|
|
122
|
+
if (!api.config) {
|
|
123
|
+
api.logger.debug?.("clawchat-plugin-openclaw: api.config missing; skipping tool registration");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Re-resolve at call time so config reloads pick up new tokens / baseUrl.
|
|
127
|
+
function resolveCurrent() {
|
|
128
|
+
return resolveOpenclawClawlingAccount(api.config);
|
|
129
|
+
}
|
|
130
|
+
function resolveStore() {
|
|
131
|
+
if ("store" in options)
|
|
132
|
+
return options.store ?? null;
|
|
133
|
+
try {
|
|
134
|
+
const stateDir = api.runtime.state?.resolveStateDir?.();
|
|
135
|
+
return getClawChatStore({
|
|
136
|
+
...(stateDir ? { dbPath: clawChatDbPathForStateDir(stateDir) } : {}),
|
|
137
|
+
log: { error: (message) => api.logger.error?.(message) },
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
api.logger.error?.("clawchat-plugin-openclaw sqlite tool persistence unavailable; continuing");
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function detailsError(details) {
|
|
146
|
+
if (!details || typeof details !== "object")
|
|
147
|
+
return null;
|
|
148
|
+
const raw = details;
|
|
149
|
+
if (typeof raw.error !== "string" || !raw.error)
|
|
150
|
+
return null;
|
|
151
|
+
return typeof raw.message === "string" && raw.message
|
|
152
|
+
? `${raw.error}: ${raw.message}`
|
|
153
|
+
: raw.error;
|
|
154
|
+
}
|
|
155
|
+
function persistToolCall(input) {
|
|
156
|
+
try {
|
|
157
|
+
resolveStore()?.recordToolCall(input);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
api.logger.error?.("clawchat-plugin-openclaw sqlite tool call insert failed; continuing");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function recordClawchatToolCall(toolName, params, fn) {
|
|
164
|
+
const startedAt = Date.now();
|
|
165
|
+
const account = resolveCurrent();
|
|
166
|
+
try {
|
|
167
|
+
const result = await fn();
|
|
168
|
+
const details = result.details ?? result;
|
|
169
|
+
persistToolCall({
|
|
170
|
+
platform: "openclaw",
|
|
171
|
+
accountId: account.accountId,
|
|
172
|
+
toolName,
|
|
173
|
+
args: params ?? {},
|
|
174
|
+
result: details,
|
|
175
|
+
error: detailsError(details),
|
|
176
|
+
startedAt,
|
|
177
|
+
endedAt: Date.now(),
|
|
178
|
+
});
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
persistToolCall({
|
|
183
|
+
platform: "openclaw",
|
|
184
|
+
accountId: account.accountId,
|
|
185
|
+
toolName,
|
|
186
|
+
args: params ?? {},
|
|
187
|
+
result: null,
|
|
188
|
+
error: err instanceof Error ? err.message : String(err),
|
|
189
|
+
startedAt,
|
|
190
|
+
endedAt: Date.now(),
|
|
191
|
+
});
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function buildClient() {
|
|
196
|
+
const acct = resolveCurrent();
|
|
197
|
+
// `baseUrl` always resolves via the built-in default in config.ts, so we
|
|
198
|
+
// only need to gate on `token` here (which is populated by ClawChat
|
|
199
|
+
// activation/login).
|
|
200
|
+
if (!acct.token) {
|
|
201
|
+
return { ok: false, error: configError("clawchat-plugin-openclaw: token is required") };
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
ok: true,
|
|
205
|
+
client: createOpenclawClawlingApiClient({
|
|
206
|
+
baseUrl: acct.baseUrl,
|
|
207
|
+
token: acct.token,
|
|
208
|
+
userId: acct.userId,
|
|
209
|
+
}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function withClient(fn) {
|
|
213
|
+
return (async () => {
|
|
214
|
+
const built = buildClient();
|
|
215
|
+
if (!built.ok)
|
|
216
|
+
return built.error;
|
|
217
|
+
try {
|
|
218
|
+
const data = await fn(built.client);
|
|
219
|
+
return jsonResponse(data);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
if (err instanceof ClawlingApiError)
|
|
223
|
+
return apiError(err);
|
|
224
|
+
return genericError(err);
|
|
225
|
+
}
|
|
226
|
+
})();
|
|
227
|
+
}
|
|
228
|
+
function workspaceRoot(ctx) {
|
|
229
|
+
const root = typeof ctx.workspaceDir === "string" ? ctx.workspaceDir.trim() : "";
|
|
230
|
+
return root || null;
|
|
231
|
+
}
|
|
232
|
+
function targetFromParams(params) {
|
|
233
|
+
return { targetType: params.targetType, targetId: params.targetId };
|
|
234
|
+
}
|
|
235
|
+
function validateTarget(root, params) {
|
|
236
|
+
try {
|
|
237
|
+
resolveClawChatMemoryPath(root, targetFromParams(params));
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
return err instanceof Error ? err.message : String(err);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function normalizeReadBounds(params) {
|
|
245
|
+
const offset = typeof params.offset === "number" && Number.isFinite(params.offset)
|
|
246
|
+
? Math.max(0, Math.trunc(params.offset))
|
|
247
|
+
: 0;
|
|
248
|
+
const limit = typeof params.limit === "number" && Number.isFinite(params.limit)
|
|
249
|
+
? Math.min(MEMORY_READ_MAX_LIMIT, Math.max(1, Math.trunc(params.limit)))
|
|
250
|
+
: MEMORY_READ_DEFAULT_LIMIT;
|
|
251
|
+
return { offset, limit };
|
|
252
|
+
}
|
|
253
|
+
function metadataPatchAllowedKeys(targetType) {
|
|
254
|
+
if (targetType === "group")
|
|
255
|
+
return ["group_title", "group_description"];
|
|
256
|
+
if (targetType === "owner")
|
|
257
|
+
return ["agent_behavior"];
|
|
258
|
+
return ["nickname", "avatar_url", "bio"];
|
|
259
|
+
}
|
|
260
|
+
function validateMetadataUpdatePatch(params) {
|
|
261
|
+
const patch = params.patch;
|
|
262
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
|
|
263
|
+
return "clawchat-plugin-openclaw: metadata patch is required";
|
|
264
|
+
}
|
|
265
|
+
const keys = Object.keys(patch);
|
|
266
|
+
if (keys.length === 0) {
|
|
267
|
+
return "clawchat-plugin-openclaw: metadata patch must include at least one mutable field";
|
|
268
|
+
}
|
|
269
|
+
const allowed = new Set(metadataPatchAllowedKeys(params.targetType));
|
|
270
|
+
const invalid = keys.filter((key) => !allowed.has(key));
|
|
271
|
+
if (invalid.length > 0) {
|
|
272
|
+
return `clawchat-plugin-openclaw: metadata patch contains unsupported field(s): ${invalid.join(", ")}`;
|
|
273
|
+
}
|
|
274
|
+
const nonString = keys.filter((key) => typeof patch[key] !== "string");
|
|
275
|
+
if (nonString.length > 0) {
|
|
276
|
+
return `clawchat-plugin-openclaw: metadata patch field(s) must be strings: ${nonString.join(", ")}`;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function isUnsafeMemoryPathError(message) {
|
|
281
|
+
return /Unsafe clawchat memory|Resolved clawchat memory path is outside root/i.test(message);
|
|
282
|
+
}
|
|
283
|
+
function memoryToolError(err) {
|
|
284
|
+
if (err instanceof ClawlingApiError)
|
|
285
|
+
return apiError(err);
|
|
286
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
287
|
+
if (isUnsafeMemoryPathError(message)) {
|
|
288
|
+
return validationError("clawchat-plugin-openclaw: unsafe ClawChat memory target");
|
|
289
|
+
}
|
|
290
|
+
return validationError(message);
|
|
291
|
+
}
|
|
292
|
+
function registerMemoryTools() {
|
|
293
|
+
api.registerTool((ctx) => ({
|
|
294
|
+
name: "clawchat_memory_search",
|
|
295
|
+
label: "Search ClawChat Memory",
|
|
296
|
+
description: memoryToolDescription("Search local ClawChat memory Markdown files by keyword across owner.md, users/*.md, and groups/*.md. " +
|
|
297
|
+
"Use this when the user asks who/what a remembered person, alias, relationship, prior note, group rule, group context, or local ClawChat memory item is and no explicit targetId is known. " +
|
|
298
|
+
"This searches local memory metadata and agent-authored Markdown body. It does not contact the ClawChat server. " +
|
|
299
|
+
"Use this before answering unknown when the user provides a name, alias, phrase, or uncertain reference that may exist in local memory. " +
|
|
300
|
+
"If exactly one relevant result is found, use clawchat_memory_read with the returned targetType and targetId when full context is needed. If multiple relevant results are found, summarize the candidates or ask the user to clarify."),
|
|
301
|
+
parameters: ClawchatMemorySearchSchema,
|
|
302
|
+
async execute(_callId, params) {
|
|
303
|
+
const root = workspaceRoot(ctx);
|
|
304
|
+
if (!root) {
|
|
305
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const p = params;
|
|
309
|
+
return jsonResponse(await searchClawChatMemory(root, {
|
|
310
|
+
query: p.query,
|
|
311
|
+
targetTypes: p.targetTypes,
|
|
312
|
+
maxResults: p.maxResults,
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
return memoryToolError(err);
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
}), { name: "clawchat_memory_search" });
|
|
320
|
+
api.registerTool((ctx) => ({
|
|
321
|
+
name: "clawchat_memory_read",
|
|
322
|
+
label: "Read ClawChat Memory",
|
|
323
|
+
description: memoryToolDescription("Read one local ClawChat memory Markdown file by explicit targetType and targetId. " +
|
|
324
|
+
"Use this only when the memory target is already known, such as owner, a concrete userId, a concrete groupId, the current sender_id, or a target returned by clawchat_memory_search. " +
|
|
325
|
+
"Do not guess targetId from names, nicknames, aliases, or plain text. If the user gives a name, alias, phrase, relationship, or uncertain reference, use clawchat_memory_search first. " +
|
|
326
|
+
"This reads metadata and agent-authored body; it does not contact the ClawChat server."),
|
|
327
|
+
parameters: ClawchatMemoryReadSchema,
|
|
328
|
+
async execute(_callId, params) {
|
|
329
|
+
const root = workspaceRoot(ctx);
|
|
330
|
+
if (!root) {
|
|
331
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const p = params;
|
|
335
|
+
const targetError = validateTarget(root, p);
|
|
336
|
+
if (targetError)
|
|
337
|
+
return memoryToolError(new Error(targetError));
|
|
338
|
+
const { offset, limit } = normalizeReadBounds(p);
|
|
339
|
+
const file = await readClawChatMemoryFile(root, targetFromParams(p));
|
|
340
|
+
if (!file.exists) {
|
|
341
|
+
return jsonResponse({
|
|
342
|
+
exists: false,
|
|
343
|
+
content: "",
|
|
344
|
+
offset,
|
|
345
|
+
limit,
|
|
346
|
+
truncated: false,
|
|
347
|
+
totalLength: 0,
|
|
348
|
+
nextOffset: null,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
const totalLength = file.raw.length;
|
|
352
|
+
const end = Math.min(totalLength, offset + limit);
|
|
353
|
+
const truncated = end < totalLength;
|
|
354
|
+
return jsonResponse({
|
|
355
|
+
exists: true,
|
|
356
|
+
content: file.raw.slice(offset, end),
|
|
357
|
+
offset,
|
|
358
|
+
limit,
|
|
359
|
+
truncated,
|
|
360
|
+
totalLength,
|
|
361
|
+
nextOffset: truncated ? end : null,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
return memoryToolError(err);
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
}), { name: "clawchat_memory_read" });
|
|
369
|
+
api.registerTool((ctx) => ({
|
|
370
|
+
name: "clawchat_memory_write",
|
|
371
|
+
label: "Write ClawChat Memory",
|
|
372
|
+
description: memoryToolDescription("Append to or replace only the agent-authored body of a ClawChat memory Markdown file by explicit targetType and targetId. " +
|
|
373
|
+
"This never modifies the metadata block. " +
|
|
374
|
+
"Do not use this to write or refresh ClawChat profile/metadata fields such as agent_nickname, agent_avatar_url, agent_bio, agent_behavior, agent_owner_nickname, agent_owner_avatar_url, agent_owner_bio, nickname, avatar_url, bio, profile_type, group_title, or group_description. " +
|
|
375
|
+
"When the user asks to refresh, sync, or update local ClawChat current-agent/agent-owner/user/group profile information, use clawchat_metadata_sync with direction=pull instead. " +
|
|
376
|
+
"Do not use this to search memory. Use clawchat_memory_search to locate uncertain names, aliases, relationships, or prior notes before writing. " +
|
|
377
|
+
"Use append for new long-term memory notes and replace only when intentionally rewriting the whole body."),
|
|
378
|
+
parameters: ClawchatMemoryWriteSchema,
|
|
379
|
+
async execute(_callId, params) {
|
|
380
|
+
const root = workspaceRoot(ctx);
|
|
381
|
+
if (!root) {
|
|
382
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
const p = params;
|
|
386
|
+
const targetError = validateTarget(root, p);
|
|
387
|
+
if (targetError)
|
|
388
|
+
return memoryToolError(new Error(targetError));
|
|
389
|
+
await writeClawChatMemoryBody(root, targetFromParams(p), p.mode, p.content);
|
|
390
|
+
return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId, mode: p.mode });
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
return memoryToolError(err);
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
}), { name: "clawchat_memory_write" });
|
|
397
|
+
api.registerTool((ctx) => ({
|
|
398
|
+
name: "clawchat_memory_edit",
|
|
399
|
+
label: "Edit ClawChat Memory",
|
|
400
|
+
description: memoryToolDescription("Replace exactly one existing text span in the agent-authored body of a ClawChat memory Markdown file. " +
|
|
401
|
+
"This never modifies the metadata block. " +
|
|
402
|
+
"Do not use this to edit ClawChat profile/metadata fields such as agent_nickname, agent_avatar_url, agent_bio, agent_behavior, agent_owner_nickname, agent_owner_avatar_url, agent_owner_bio, nickname, avatar_url, bio, profile_type, group_title, or group_description. " +
|
|
403
|
+
"Use clawchat_metadata_sync or clawchat_metadata_update for metadata. " +
|
|
404
|
+
"Do not use this to search memory. Use clawchat_memory_search to locate uncertain names, aliases, relationships, or prior notes before editing. " +
|
|
405
|
+
"The oldText must match exactly once; use read first when unsure."),
|
|
406
|
+
parameters: ClawchatMemoryEditSchema,
|
|
407
|
+
async execute(_callId, params) {
|
|
408
|
+
const root = workspaceRoot(ctx);
|
|
409
|
+
if (!root) {
|
|
410
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const p = params;
|
|
414
|
+
const targetError = validateTarget(root, p);
|
|
415
|
+
if (targetError)
|
|
416
|
+
return memoryToolError(new Error(targetError));
|
|
417
|
+
await editClawChatMemoryBody(root, targetFromParams(p), p.oldText, p.newText);
|
|
418
|
+
return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId });
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
return memoryToolError(err);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
}), { name: "clawchat_memory_edit" });
|
|
425
|
+
api.registerTool((ctx) => ({
|
|
426
|
+
name: "clawchat_metadata_sync",
|
|
427
|
+
label: "Sync ClawChat Metadata",
|
|
428
|
+
description: memoryToolDescription("Synchronize the ClawChat metadata block for an explicit owner, user, or group target. " +
|
|
429
|
+
"Use direction=pull when the user asks to refresh, sync, or update local ClawChat current agent profile/behavior, agent-owner profile information, user information, or group information from the server; this fetches the authoritative server record and rewrites only the metadata block. " +
|
|
430
|
+
"Use direction=push only to push selected existing local metadata fields to the server, then refresh the metadata block from the server response. " +
|
|
431
|
+
"For direction=push, pass non-empty fields containing only pushable metadata field names: owner supports agent_behavior only; user supports nickname/avatar_url/bio for the connected account only; group supports group_title/group_description. " +
|
|
432
|
+
"This does not modify the agent-authored body. " +
|
|
433
|
+
"This synchronizes only the metadata block. It does not search or read agent-authored long-term memory body. For remembered aliases or local notes, use clawchat_memory_search or clawchat_memory_read. " +
|
|
434
|
+
"Do not combine clawchat_get_user_profile with clawchat_memory_write to update local profile metadata."),
|
|
435
|
+
parameters: ClawchatMetadataSyncSchema,
|
|
436
|
+
async execute(_callId, params) {
|
|
437
|
+
const root = workspaceRoot(ctx);
|
|
438
|
+
if (!root) {
|
|
439
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
440
|
+
}
|
|
441
|
+
const built = buildClient();
|
|
442
|
+
if (!built.ok)
|
|
443
|
+
return built.error;
|
|
444
|
+
try {
|
|
445
|
+
const p = params;
|
|
446
|
+
const targetError = validateTarget(root, p);
|
|
447
|
+
if (targetError)
|
|
448
|
+
return memoryToolError(new Error(targetError));
|
|
449
|
+
const account = resolveCurrent();
|
|
450
|
+
if (p.direction === "push") {
|
|
451
|
+
if (!Array.isArray(p.fields) || p.fields.length === 0) {
|
|
452
|
+
return validationError("clawchat-plugin-openclaw: fields are required for metadata push");
|
|
453
|
+
}
|
|
454
|
+
if (p.targetType === "owner" && !account.agentId) {
|
|
455
|
+
return configError("clawchat-plugin-openclaw: agentId is required");
|
|
456
|
+
}
|
|
457
|
+
if (p.targetType === "user" && !account.userId) {
|
|
458
|
+
return configError("clawchat-plugin-openclaw: userId is required");
|
|
459
|
+
}
|
|
460
|
+
return jsonResponse(await pushMetadata({
|
|
461
|
+
memoryRoot: root,
|
|
462
|
+
targetType: p.targetType,
|
|
463
|
+
targetId: p.targetId,
|
|
464
|
+
fields: p.fields,
|
|
465
|
+
agentId: account.agentId,
|
|
466
|
+
accountUserId: account.userId,
|
|
467
|
+
api: built.client,
|
|
468
|
+
}));
|
|
469
|
+
}
|
|
470
|
+
if (p.targetType === "owner") {
|
|
471
|
+
if (!account.agentId)
|
|
472
|
+
return configError("clawchat-plugin-openclaw: agentId is required");
|
|
473
|
+
return jsonResponse(await pullOwnerMetadata({
|
|
474
|
+
memoryRoot: root,
|
|
475
|
+
agentId: account.agentId,
|
|
476
|
+
accountUserId: account.userId,
|
|
477
|
+
accountOwnerUserId: account.ownerUserId || undefined,
|
|
478
|
+
api: built.client,
|
|
479
|
+
}));
|
|
480
|
+
}
|
|
481
|
+
if (p.targetType === "user") {
|
|
482
|
+
return jsonResponse(await pullUserMetadata({ memoryRoot: root, userId: p.targetId, api: built.client }));
|
|
483
|
+
}
|
|
484
|
+
return jsonResponse(await pullGroupMetadata({
|
|
485
|
+
memoryRoot: root,
|
|
486
|
+
groupId: p.targetId,
|
|
487
|
+
api: built.client,
|
|
488
|
+
skipUserIds: [account.userId, account.ownerUserId],
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
return memoryToolError(err);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
}), { name: "clawchat_metadata_sync" });
|
|
496
|
+
api.registerTool((ctx) => ({
|
|
497
|
+
name: "clawchat_metadata_update",
|
|
498
|
+
label: "Update ClawChat Metadata",
|
|
499
|
+
description: memoryToolDescription("Update ClawChat server metadata for an explicit owner, connected user account, or group target, then refresh the local metadata block from the server response. " +
|
|
500
|
+
"Use this only when the user wants to change server-side metadata fields: current agent behavior via owner target agent_behavior, connected-user nickname/avatar_url/bio, or group group_title/group_description. " +
|
|
501
|
+
"To refresh local metadata from the server without changing the server, use clawchat_metadata_sync with direction=pull. " +
|
|
502
|
+
"This always pushes to the server first and does not modify the agent-authored body."),
|
|
503
|
+
parameters: ClawchatMetadataUpdateSchema,
|
|
504
|
+
async execute(_callId, params) {
|
|
505
|
+
const root = workspaceRoot(ctx);
|
|
506
|
+
if (!root) {
|
|
507
|
+
return validationError("clawchat-plugin-openclaw: OpenClaw workspaceDir is required");
|
|
508
|
+
}
|
|
509
|
+
const built = buildClient();
|
|
510
|
+
if (!built.ok)
|
|
511
|
+
return built.error;
|
|
512
|
+
try {
|
|
513
|
+
const p = params;
|
|
514
|
+
const targetError = validateTarget(root, p);
|
|
515
|
+
if (targetError)
|
|
516
|
+
return memoryToolError(new Error(targetError));
|
|
517
|
+
const patchError = validateMetadataUpdatePatch(p);
|
|
518
|
+
if (patchError)
|
|
519
|
+
return validationError(patchError);
|
|
520
|
+
const account = resolveCurrent();
|
|
521
|
+
if (p.targetType === "owner" && !account.agentId) {
|
|
522
|
+
return configError("clawchat-plugin-openclaw: agentId is required");
|
|
523
|
+
}
|
|
524
|
+
if (p.targetType === "user" && !account.userId) {
|
|
525
|
+
return configError("clawchat-plugin-openclaw: userId is required");
|
|
526
|
+
}
|
|
527
|
+
return jsonResponse(await updateMetadata({
|
|
528
|
+
memoryRoot: root,
|
|
529
|
+
targetType: p.targetType,
|
|
530
|
+
targetId: p.targetId,
|
|
531
|
+
agentId: account.agentId,
|
|
532
|
+
accountUserId: account.userId,
|
|
533
|
+
patch: p.patch,
|
|
534
|
+
api: built.client,
|
|
535
|
+
}));
|
|
536
|
+
}
|
|
537
|
+
catch (err) {
|
|
538
|
+
return memoryToolError(err);
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
}), { name: "clawchat_metadata_update" });
|
|
542
|
+
}
|
|
543
|
+
registerMemoryTools();
|
|
544
|
+
api.registerTool({
|
|
545
|
+
name: "clawchat_get_account_profile",
|
|
546
|
+
label: "Get ClawChat Account Profile",
|
|
547
|
+
description: toolDescription("Fetch the agent's connected ClawChat account profile (the configured ClawChat account: user id, nickname/display name, avatar, bio). " +
|
|
548
|
+
"This profile is the platform-side mirror of the local assistant identity; if fields are missing, report them as unset instead of inventing values. " +
|
|
549
|
+
"TRIGGER — invoke when the user asks for the ClawChat account/profile connected to this agent, " +
|
|
550
|
+
"such as 'show my ClawChat profile', 'what is the configured ClawChat account?', " +
|
|
551
|
+
"'当前 ClawChat 账号资料', or 'ClawChat 昵称头像简介'. " +
|
|
552
|
+
"Do not frame this as a human user's personal account."),
|
|
553
|
+
parameters: ClawchatGetAccountProfileSchema,
|
|
554
|
+
async execute(_callId, params) {
|
|
555
|
+
return await recordClawchatToolCall("clawchat_get_account_profile", params, async () => withClient((c) => c.getMyProfile()));
|
|
556
|
+
},
|
|
557
|
+
}, { name: "clawchat_get_account_profile" });
|
|
558
|
+
api.registerTool({
|
|
559
|
+
name: "clawchat_get_user_profile",
|
|
560
|
+
label: "Get ClawChat User Profile",
|
|
561
|
+
description: toolDescription("Fetch a ClawChat user's server-side public profile by explicit userId. " +
|
|
562
|
+
"TRIGGER — invoke when the user asks to view or inspect a specific ClawChat user's public profile and provides a concrete userId, or after clawchat_search_users returns a userId. " +
|
|
563
|
+
"Do not guess or infer userId from nickname, display name, alias, or local memory text. " +
|
|
564
|
+
"This is a read-only lookup and server lookup. It does not read local ClawChat memory files and does not update local metadata. " +
|
|
565
|
+
"When the user asks to refresh, sync, or update that user's local profile information, call clawchat_metadata_sync with targetType=user, that targetId, and direction=pull. " +
|
|
566
|
+
"Use `clawchat_get_account_profile` for the agent's own connected ClawChat account unless an explicit userId is provided."),
|
|
567
|
+
parameters: ClawchatGetUserProfileSchema,
|
|
568
|
+
async execute(_callId, params) {
|
|
569
|
+
return await recordClawchatToolCall("clawchat_get_user_profile", params, async () => {
|
|
570
|
+
const p = params;
|
|
571
|
+
return await withClient((c) => c.getUserInfo(p.userId));
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
}, { name: "clawchat_get_user_profile" });
|
|
575
|
+
api.registerTool({
|
|
576
|
+
name: "clawchat_list_account_friends",
|
|
577
|
+
label: "List ClawChat Account Friends",
|
|
578
|
+
description: toolDescription("List friends/contacts of the agent's connected ClawChat account (the configured ClawChat account). " +
|
|
579
|
+
"These are the agent's ClawChat-platform contacts. " +
|
|
580
|
+
"TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, or friend list."),
|
|
581
|
+
parameters: ClawchatListAccountFriendsSchema,
|
|
582
|
+
async execute(_callId, params) {
|
|
583
|
+
return await recordClawchatToolCall("clawchat_list_account_friends", params, async () => withClient((c) => c.listFriends()));
|
|
584
|
+
},
|
|
585
|
+
}, { name: "clawchat_list_account_friends" });
|
|
586
|
+
api.registerTool({
|
|
587
|
+
name: "clawchat_search_users",
|
|
588
|
+
label: "Search ClawChat Users",
|
|
589
|
+
description: toolDescription("Search ClawChat users by username or nickname. Search server-side ClawChat users in the ClawChat user directory. " +
|
|
590
|
+
"TRIGGER - invoke when the user asks to search, find, or look up ClawChat users in the server directory by a typed query, username, or public nickname, such as \"search ClawChat users named Alice\", \"查找用户 Alice\", or \"搜一下昵称 Alice\". " +
|
|
591
|
+
"This does not search local ClawChat memory files, aliases, known_as notes, relationship notes, group notes, or agent-authored Markdown memory. For remembered aliases, local notes, relationships, or prior ClawChat memory, use clawchat_memory_search. " +
|
|
592
|
+
"Empty q returns no users. Use this tool before fetching a public profile when the user only provides a server-side nickname or search term; do not guess a userId from query text."),
|
|
593
|
+
parameters: ClawchatSearchUsersSchema,
|
|
594
|
+
async execute(_callId, params) {
|
|
595
|
+
return await recordClawchatToolCall("clawchat_search_users", params, async () => {
|
|
596
|
+
const p = (params ?? {});
|
|
597
|
+
return await withClient((c) => c.searchUsers({
|
|
598
|
+
...(typeof p.q === "string" ? { q: p.q } : {}),
|
|
599
|
+
...(typeof p.limit === "number" ? { limit: p.limit } : {}),
|
|
600
|
+
}));
|
|
601
|
+
});
|
|
602
|
+
},
|
|
603
|
+
}, { name: "clawchat_search_users" });
|
|
604
|
+
api.registerTool({
|
|
605
|
+
name: "clawchat_get_conversation",
|
|
606
|
+
label: "Get ClawChat Conversation",
|
|
607
|
+
description: toolDescription("Fetch read-only ClawChat conversation details, including group membership when returned by the API. " +
|
|
608
|
+
"TRIGGER - invoke when the user asks to inspect a specific ClawChat conversation or group and provides a concrete conversationId. " +
|
|
609
|
+
"This is read-only and does not create, update, leave, dissolve, or administer conversations."),
|
|
610
|
+
parameters: ClawchatGetConversationSchema,
|
|
611
|
+
async execute(_callId, params) {
|
|
612
|
+
return await recordClawchatToolCall("clawchat_get_conversation", params, async () => {
|
|
613
|
+
const p = params;
|
|
614
|
+
const built = buildClient();
|
|
615
|
+
if (!built.ok)
|
|
616
|
+
return built.error;
|
|
617
|
+
try {
|
|
618
|
+
const data = await built.client.getConversation(p.conversationId);
|
|
619
|
+
return jsonResponse(data);
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
if (err instanceof ClawlingApiError) {
|
|
623
|
+
return apiError(err);
|
|
624
|
+
}
|
|
625
|
+
return genericError(err);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
},
|
|
629
|
+
}, { name: "clawchat_get_conversation" });
|
|
630
|
+
api.registerTool({
|
|
631
|
+
name: "clawchat_mention_message",
|
|
632
|
+
label: "Send ClawChat Mention Message",
|
|
633
|
+
description: toolDescription("Send a real ClawChat mention message to a direct or group conversation. " +
|
|
634
|
+
"TRIGGER - invoke when the user asks to @, mention, notify, or address ClawChat users. " +
|
|
635
|
+
"Use `mentioned_users` or `sender_id` from current `[message]` blocks as `mentions[].userId` when present. " +
|
|
636
|
+
"Use `clawchat_search_users` only when no explicit or locally available id exists. " +
|
|
637
|
+
"Never guess userId from names, nicknames, or plain @name text. Plain @name is not a real mention. " +
|
|
638
|
+
"Pass the visible label as `mentions[].display` when known. Do not copy the visible @mention into `text`; put only the message body in `text`. If display is unknown but a concrete userId is available and a human-readable label matters, call clawchat_get_user_profile first and use the returned nickname. " +
|
|
639
|
+
"After this tool succeeds, the mention message has already been sent; output only <clawchat:no-reply/> and do not send a normal follow-up reply."),
|
|
640
|
+
parameters: ClawchatMentionMessageSchema,
|
|
641
|
+
async execute(_callId, params) {
|
|
642
|
+
return await recordClawchatToolCall("clawchat_mention_message", params, async () => {
|
|
643
|
+
const validated = validateMentionMessageParams(params);
|
|
644
|
+
if (!validated.ok)
|
|
645
|
+
return validationError(validated.message);
|
|
646
|
+
const account = resolveCurrent();
|
|
647
|
+
if (!account.configured) {
|
|
648
|
+
return configError("clawchat-plugin-openclaw: account is not configured");
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
const p = validated.params;
|
|
652
|
+
const client = getOpenclawClawlingClient(account.accountId);
|
|
653
|
+
if (!client) {
|
|
654
|
+
return configError("ClawChat websocket client is not ready");
|
|
655
|
+
}
|
|
656
|
+
const result = await sendOpenclawClawlingMentionMessage({
|
|
657
|
+
client,
|
|
658
|
+
account,
|
|
659
|
+
to: {
|
|
660
|
+
chatId: p.chatId.trim(),
|
|
661
|
+
chatType: p.chatType ?? "group",
|
|
662
|
+
},
|
|
663
|
+
...(p.text !== undefined ? { text: p.text } : {}),
|
|
664
|
+
mentions: p.mentions,
|
|
665
|
+
...(p.replyToMessageId
|
|
666
|
+
? {
|
|
667
|
+
replyCtx: {
|
|
668
|
+
replyToMessageId: p.replyToMessageId,
|
|
669
|
+
},
|
|
670
|
+
}
|
|
671
|
+
: {}),
|
|
672
|
+
log: api.logger,
|
|
673
|
+
});
|
|
674
|
+
if (!result)
|
|
675
|
+
return jsonResponse(result);
|
|
676
|
+
markTerminalClawChatSend({
|
|
677
|
+
accountId: account.accountId,
|
|
678
|
+
chatId: p.chatId.trim(),
|
|
679
|
+
messageId: result.messageId,
|
|
680
|
+
});
|
|
681
|
+
return jsonResponse({
|
|
682
|
+
sent: true,
|
|
683
|
+
terminal: true,
|
|
684
|
+
noFollowupReply: true,
|
|
685
|
+
instruction: "The mention message has already been sent to ClawChat. If a follow-up response is required, output only <clawchat:no-reply/> and do not send a normal follow-up reply.",
|
|
686
|
+
...result,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
catch (err) {
|
|
690
|
+
return genericError(err);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
},
|
|
694
|
+
}, { name: "clawchat_mention_message" });
|
|
695
|
+
api.registerTool({
|
|
696
|
+
name: "clawchat_list_moments",
|
|
697
|
+
label: "List ClawChat Moments",
|
|
698
|
+
description: toolDescription("List the configured ClawChat account's visible moments feed, including moments from the account and its friends. " +
|
|
699
|
+
"TRIGGER - invoke when the user asks to view, browse, refresh, or paginate ClawChat moments/dynamics/feed, such as \"show my ClawChat moments\", \"查看动态\", \"朋友圈动态\", or \"more moments\". " +
|
|
700
|
+
"Use before/comment/reaction/delete actions when the user needs to choose a moment id. This is a friends-only feed endpoint, not a global public timeline."),
|
|
701
|
+
parameters: ClawchatListMomentsSchema,
|
|
702
|
+
async execute(_callId, params) {
|
|
703
|
+
return await recordClawchatToolCall("clawchat_list_moments", params, async () => {
|
|
704
|
+
const p = (params ?? {});
|
|
705
|
+
return await withClient((c) => c.listMoments({
|
|
706
|
+
...(typeof p.before === "number" ? { before: p.before } : {}),
|
|
707
|
+
...(typeof p.limit === "number" ? { limit: p.limit } : {}),
|
|
708
|
+
}));
|
|
709
|
+
});
|
|
710
|
+
},
|
|
711
|
+
}, { name: "clawchat_list_moments" });
|
|
712
|
+
api.registerTool({
|
|
713
|
+
name: "clawchat_create_moment",
|
|
714
|
+
label: "Create ClawChat Moment",
|
|
715
|
+
description: toolDescription("Create a new ClawChat moment/dynamic for the configured ClawChat account. " +
|
|
716
|
+
"TRIGGER - invoke when the user asks to publish, post, or send a ClawChat moment/dynamic, such as \"post a ClawChat moment saying ...\", \"发布动态 ...\", or \"发朋友圈 ...\". " +
|
|
717
|
+
"At least one of text or images must be present. For local image files, upload first with the appropriate media upload tool and pass the returned URLs in images; do not pass local file paths as images."),
|
|
718
|
+
parameters: ClawchatCreateMomentSchema,
|
|
719
|
+
async execute(_callId, params) {
|
|
720
|
+
return await recordClawchatToolCall("clawchat_create_moment", params, async () => {
|
|
721
|
+
const p = (params ?? {});
|
|
722
|
+
const text = typeof p.text === "string" ? p.text : undefined;
|
|
723
|
+
const images = Array.isArray(p.images) ? p.images : undefined;
|
|
724
|
+
if (!text && (!images || images.length === 0)) {
|
|
725
|
+
return validationError("clawchat-plugin-openclaw: at least one of text or images is required");
|
|
726
|
+
}
|
|
727
|
+
return await withClient((c) => c.createMoment({
|
|
728
|
+
...(text !== undefined ? { text } : {}),
|
|
729
|
+
...(images !== undefined ? { images } : {}),
|
|
730
|
+
}));
|
|
731
|
+
});
|
|
732
|
+
},
|
|
733
|
+
}, { name: "clawchat_create_moment" });
|
|
734
|
+
api.registerTool({
|
|
735
|
+
name: "clawchat_delete_moment",
|
|
736
|
+
label: "Delete ClawChat Moment",
|
|
737
|
+
description: toolDescription("Delete a ClawChat moment by moment id. " +
|
|
738
|
+
"TRIGGER - invoke when the user asks to delete/remove one of the configured account's ClawChat moments/dynamics and provides or selects a concrete moment id. " +
|
|
739
|
+
"Only the moment author can delete it. Do not guess the id; list moments first if the user refers to a moment ambiguously."),
|
|
740
|
+
parameters: ClawchatDeleteMomentSchema,
|
|
741
|
+
async execute(_callId, params) {
|
|
742
|
+
return await recordClawchatToolCall("clawchat_delete_moment", params, async () => {
|
|
743
|
+
const p = params;
|
|
744
|
+
return await withClient((c) => c.deleteMoment(p.momentId));
|
|
745
|
+
});
|
|
746
|
+
},
|
|
747
|
+
}, { name: "clawchat_delete_moment" });
|
|
748
|
+
api.registerTool({
|
|
749
|
+
name: "clawchat_toggle_moment_reaction",
|
|
750
|
+
label: "Toggle ClawChat Moment Reaction",
|
|
751
|
+
description: toolDescription("Toggle an emoji reaction on a ClawChat moment. " +
|
|
752
|
+
"TRIGGER - invoke when the user asks to react, like, unlike, emoji-react, or remove the same emoji reaction on a specific ClawChat moment, such as \"like moment 123 with 👍\", \"给动态 123 点赞\", or \"取消这个 👍 反应\". " +
|
|
753
|
+
"The API adds the reaction if missing and removes it if already present. Require a concrete moment id and emoji."),
|
|
754
|
+
parameters: ClawchatToggleMomentReactionSchema,
|
|
755
|
+
async execute(_callId, params) {
|
|
756
|
+
return await recordClawchatToolCall("clawchat_toggle_moment_reaction", params, async () => {
|
|
757
|
+
const p = params;
|
|
758
|
+
if (!p.emoji?.trim()) {
|
|
759
|
+
return validationError("clawchat-plugin-openclaw: emoji is required");
|
|
760
|
+
}
|
|
761
|
+
return await withClient((c) => c.toggleMomentReaction({ momentId: p.momentId, emoji: p.emoji }));
|
|
762
|
+
});
|
|
763
|
+
},
|
|
764
|
+
}, { name: "clawchat_toggle_moment_reaction" });
|
|
765
|
+
api.registerTool({
|
|
766
|
+
name: "clawchat_create_moment_comment",
|
|
767
|
+
label: "Create ClawChat Moment Comment",
|
|
768
|
+
description: toolDescription("Create a top-level comment on a ClawChat moment. " +
|
|
769
|
+
"TRIGGER - invoke when the user asks to comment/reply directly to a moment/dynamic, not to another comment, such as \"comment on moment 123: ...\", \"评论动态 123 ...\", or \"在这条动态下留言 ...\". " +
|
|
770
|
+
"Require a concrete moment id and non-empty text. Use clawchat_reply_moment_comment when the user is replying to another user's comment."),
|
|
771
|
+
parameters: ClawchatCreateMomentCommentSchema,
|
|
772
|
+
async execute(_callId, params) {
|
|
773
|
+
return await recordClawchatToolCall("clawchat_create_moment_comment", params, async () => {
|
|
774
|
+
const p = params;
|
|
775
|
+
if (!p.text?.trim()) {
|
|
776
|
+
return validationError("clawchat-plugin-openclaw: text is required");
|
|
777
|
+
}
|
|
778
|
+
return await withClient((c) => c.createMomentComment({ momentId: p.momentId, text: p.text }));
|
|
779
|
+
});
|
|
780
|
+
},
|
|
781
|
+
}, { name: "clawchat_create_moment_comment" });
|
|
782
|
+
api.registerTool({
|
|
783
|
+
name: "clawchat_reply_moment_comment",
|
|
784
|
+
label: "Reply To ClawChat Moment Comment",
|
|
785
|
+
description: toolDescription("Reply to an existing ClawChat moment comment with a single-level reply. " +
|
|
786
|
+
"TRIGGER - invoke when the user asks to reply to another user's comment on a moment/dynamic, such as \"reply to comment 456 on moment 123: ...\", \"回复评论 456 ...\", or \"回复他那条评论 ...\". " +
|
|
787
|
+
"Require concrete moment and comment ids; do not use this for top-level comments."),
|
|
788
|
+
parameters: ClawchatReplyMomentCommentSchema,
|
|
789
|
+
async execute(_callId, params) {
|
|
790
|
+
return await recordClawchatToolCall("clawchat_reply_moment_comment", params, async () => {
|
|
791
|
+
const p = params;
|
|
792
|
+
if (!p.text?.trim()) {
|
|
793
|
+
return validationError("clawchat-plugin-openclaw: text is required");
|
|
794
|
+
}
|
|
795
|
+
return await withClient((c) => c.replyMomentComment({
|
|
796
|
+
momentId: p.momentId,
|
|
797
|
+
replyToCommentId: p.replyToCommentId,
|
|
798
|
+
text: p.text,
|
|
799
|
+
}));
|
|
800
|
+
});
|
|
801
|
+
},
|
|
802
|
+
}, { name: "clawchat_reply_moment_comment" });
|
|
803
|
+
api.registerTool({
|
|
804
|
+
name: "clawchat_delete_moment_comment",
|
|
805
|
+
label: "Delete ClawChat Moment Comment",
|
|
806
|
+
description: toolDescription("Delete a comment on a ClawChat moment. " +
|
|
807
|
+
"TRIGGER - invoke when the user asks to delete/remove a specific comment or reply from a ClawChat moment/dynamic and provides concrete moment and comment ids. " +
|
|
808
|
+
"The caller may delete comments they authored or comments on moments they authored. Do not guess ids; list moments first if needed."),
|
|
809
|
+
parameters: ClawchatDeleteMomentCommentSchema,
|
|
810
|
+
async execute(_callId, params) {
|
|
811
|
+
return await recordClawchatToolCall("clawchat_delete_moment_comment", params, async () => {
|
|
812
|
+
const p = params;
|
|
813
|
+
return await withClient((c) => c.deleteMomentComment({ momentId: p.momentId, commentId: p.commentId }));
|
|
814
|
+
});
|
|
815
|
+
},
|
|
816
|
+
}, { name: "clawchat_delete_moment_comment" });
|
|
817
|
+
api.registerTool({
|
|
818
|
+
name: "clawchat_update_account_profile",
|
|
819
|
+
label: "Update ClawChat Account Profile",
|
|
820
|
+
description: toolDescription("Update nickname/avatar_url/bio on the agent's connected ClawChat account (the configured ClawChat account), which mirrors the local assistant identity. " +
|
|
821
|
+
"TRIGGER — invoke this tool whenever the user's message asks to change the ClawChat account profile or local assistant name/profile while ClawChat is connected: " +
|
|
822
|
+
"(1) ClawChat account nickname/name change: 'change the ClawChat account nickname to X', " +
|
|
823
|
+
"'set this assistant name to X', 'ClawChat 昵称改为 X', '账号昵称改成 X', '账号名字叫 X' " +
|
|
824
|
+
"→ call with `nickname = X`; " +
|
|
825
|
+
"(2) ClawChat account avatar/profile-picture change: 'change the ClawChat account avatar', " +
|
|
826
|
+
"'use this image as the assistant profile picture', 'ClawChat 头像改为 …', '账号头像换成 …' " +
|
|
827
|
+
"→ first obtain the avatar URL (upload via `clawchat_upload_avatar_image`, OR use a provided URL directly), " +
|
|
828
|
+
"then call this tool with `avatar_url = <url>`; " +
|
|
829
|
+
"(3) ClawChat account bio/self-introduction change: 'update the ClawChat bio', " +
|
|
830
|
+
"'set the assistant self-introduction to X', 'ClawChat 简介改成 X', '账号简介改为 X', '个人简介改为 X' " +
|
|
831
|
+
"→ call with `bio = X`. " +
|
|
832
|
+
"You can pass `nickname`, `avatar_url`, and `bio` together in one call, or just one of them. " +
|
|
833
|
+
"At least one of the three must be present. Do not frame this as updating a human user's personal account."),
|
|
834
|
+
parameters: ClawchatUpdateAccountProfileSchema,
|
|
835
|
+
async execute(_callId, params) {
|
|
836
|
+
return await recordClawchatToolCall("clawchat_update_account_profile", params, async () => {
|
|
837
|
+
const p = (params ?? {});
|
|
838
|
+
const patch = {};
|
|
839
|
+
if (typeof p.nickname === "string")
|
|
840
|
+
patch.nickname = p.nickname;
|
|
841
|
+
if (typeof p.avatar_url === "string")
|
|
842
|
+
patch.avatar_url = p.avatar_url;
|
|
843
|
+
if (typeof p.bio === "string")
|
|
844
|
+
patch.bio = p.bio;
|
|
845
|
+
if (Object.keys(patch).length === 0) {
|
|
846
|
+
return validationError("clawchat-plugin-openclaw: at least one of nickname / avatar / bio is required");
|
|
847
|
+
}
|
|
848
|
+
return await withClient((c) => c.updateMyProfile(patch));
|
|
849
|
+
});
|
|
850
|
+
},
|
|
851
|
+
}, { name: "clawchat_update_account_profile" });
|
|
852
|
+
api.registerTool({
|
|
853
|
+
name: "clawchat_upload_avatar_image",
|
|
854
|
+
label: "Upload ClawChat Avatar Image",
|
|
855
|
+
description: toolDescription("Upload an absolute local image path for use as the agent's connected ClawChat account avatar (max 20MB), returning a hosted avatar URL. " +
|
|
856
|
+
"TRIGGER — invoke when the user provides an absolute local image path and asks to upload it for the ClawChat account avatar/profile picture. " +
|
|
857
|
+
"This tool does not update or set the account avatar by itself; when the user asked to set or sync the avatar, call `clawchat_update_account_profile` with `avatar_url` after this tool returns a URL."),
|
|
858
|
+
parameters: ClawchatUploadAvatarImageSchema,
|
|
859
|
+
async execute(_callId, params) {
|
|
860
|
+
return await recordClawchatToolCall("clawchat_upload_avatar_image", params, async () => {
|
|
861
|
+
const p = params;
|
|
862
|
+
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
863
|
+
return validationError("clawchat-plugin-openclaw: filePath must be an absolute local path");
|
|
864
|
+
}
|
|
865
|
+
let stat;
|
|
866
|
+
try {
|
|
867
|
+
stat = fs.statSync(p.filePath);
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
return validationError(`clawchat-plugin-openclaw: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
871
|
+
}
|
|
872
|
+
if (!stat.isFile()) {
|
|
873
|
+
return validationError(`clawchat-plugin-openclaw: ${p.filePath} is not a regular file`);
|
|
874
|
+
}
|
|
875
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
876
|
+
return validationError(`clawchat-plugin-openclaw: file too large (${stat.size} bytes; max 20MB)`);
|
|
877
|
+
}
|
|
878
|
+
const buffer = fs.readFileSync(p.filePath);
|
|
879
|
+
const filename = path.basename(p.filePath);
|
|
880
|
+
const mime = inferMimeFromPath(p.filePath);
|
|
881
|
+
return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
|
|
882
|
+
});
|
|
883
|
+
},
|
|
884
|
+
}, { name: "clawchat_upload_avatar_image" });
|
|
885
|
+
api.registerTool({
|
|
886
|
+
name: "clawchat_upload_media_file",
|
|
887
|
+
label: "Upload ClawChat Media File",
|
|
888
|
+
description: toolDescription("Upload an absolute local file/media path to ClawChat media storage (max 20MB) and return a ClawChat-accessible public/shareable URL. " +
|
|
889
|
+
"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. " +
|
|
890
|
+
"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). " +
|
|
891
|
+
"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."),
|
|
892
|
+
parameters: ClawchatUploadMediaFileSchema,
|
|
893
|
+
async execute(_callId, params) {
|
|
894
|
+
return await recordClawchatToolCall("clawchat_upload_media_file", params, async () => {
|
|
895
|
+
const p = params;
|
|
896
|
+
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
897
|
+
return validationError("clawchat-plugin-openclaw: filePath must be an absolute local path");
|
|
898
|
+
}
|
|
899
|
+
let stat;
|
|
900
|
+
try {
|
|
901
|
+
stat = fs.statSync(p.filePath);
|
|
902
|
+
}
|
|
903
|
+
catch (err) {
|
|
904
|
+
return validationError(`clawchat-plugin-openclaw: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
905
|
+
}
|
|
906
|
+
if (!stat.isFile()) {
|
|
907
|
+
return validationError(`clawchat-plugin-openclaw: ${p.filePath} is not a regular file`);
|
|
908
|
+
}
|
|
909
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
910
|
+
return validationError(`clawchat-plugin-openclaw: file too large (${stat.size} bytes; max 20MB)`);
|
|
911
|
+
}
|
|
912
|
+
const buffer = fs.readFileSync(p.filePath);
|
|
913
|
+
const filename = path.basename(p.filePath);
|
|
914
|
+
const mime = inferMimeFromPath(p.filePath);
|
|
915
|
+
return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
|
|
916
|
+
});
|
|
917
|
+
},
|
|
918
|
+
}, { name: "clawchat_upload_media_file" });
|
|
919
|
+
api.logger.debug?.("clawchat-plugin-openclaw: registered 22 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, 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)");
|
|
920
|
+
}
|