@newbase-clawchat/openclaw-clawchat 2026.5.4 → 2026.5.12-13
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 +121 -19
- package/dist/index.js +10 -19
- package/dist/setup-entry.js +3 -0
- package/dist/src/api-client.js +78 -10
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/channel.js +25 -156
- package/dist/src/channel.setup.js +120 -0
- package/dist/src/client.js +37 -41
- package/dist/src/config.js +75 -17
- package/dist/src/inbound.js +79 -61
- package/dist/src/login.runtime.js +84 -19
- package/dist/src/media-runtime.js +8 -8
- package/dist/src/message-mapper.js +1 -1
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +410 -26
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +2 -7
- package/dist/src/reply-dispatcher.js +157 -54
- package/dist/src/runtime.js +795 -119
- package/dist/src/storage.js +689 -0
- package/dist/src/tools-schema.js +98 -16
- package/dist/src/tools.js +422 -135
- 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 +10 -22
- package/openclaw.plugin.json +37 -2
- package/package.json +17 -4
- package/setup-entry.ts +4 -0
- package/skills/clawchat/SKILL.md +88 -0
- package/src/api-client.test.ts +274 -14
- package/src/api-client.ts +138 -23
- package/src/api-types.test-d.ts +12 -0
- package/src/api-types.ts +90 -4
- package/src/buffered-stream.test.ts +14 -12
- package/src/buffered-stream.ts +1 -1
- package/src/channel.outbound.test.ts +269 -60
- package/src/channel.setup.ts +146 -0
- package/src/channel.test.ts +130 -24
- package/src/channel.ts +30 -186
- package/src/client.test.ts +197 -11
- package/src/client.ts +50 -57
- package/src/config.test.ts +108 -6
- package/src/config.ts +95 -24
- package/src/inbound.test.ts +288 -37
- package/src/inbound.ts +96 -84
- package/src/login.runtime.test.ts +347 -13
- package/src/login.runtime.ts +105 -23
- package/src/manifest.test.ts +146 -74
- package/src/media-runtime.test.ts +57 -2
- package/src/media-runtime.ts +26 -17
- package/src/message-mapper.test.ts +2 -2
- package/src/message-mapper.ts +2 -2
- package/src/mock-transport.test.ts +35 -0
- package/src/mock-transport.ts +38 -0
- package/src/outbound.test.ts +694 -73
- package/src/outbound.ts +484 -31
- package/src/plugin-entry.test.ts +1 -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 +1 -6
- package/src/protocol.ts +2 -7
- package/src/reply-dispatcher.test.ts +819 -119
- package/src/reply-dispatcher.ts +202 -60
- package/src/runtime.test.ts +2120 -41
- package/src/runtime.ts +935 -142
- package/src/scripts.test.ts +85 -0
- package/src/storage.test.ts +793 -0
- package/src/storage.ts +1095 -0
- package/src/streaming.test.ts +9 -8
- package/src/streaming.ts +1 -1
- package/src/tools-schema.ts +148 -20
- package/src/tools.test.ts +377 -50
- package/src/tools.ts +574 -154
- package/src/ws-alignment.test.ts +103 -0
- package/src/ws-alignment.ts +275 -0
- package/src/ws-client.test.ts +1218 -0
- package/src/ws-client.ts +662 -0
- package/src/ws-log.test.ts +32 -0
- package/src/ws-log.ts +31 -0
- package/skills/clawchat-account-tools/SKILL.md +0 -26
- package/skills/clawchat-activate/SKILL.md +0 -47
package/dist/src/tools.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
4
|
-
import { ClawlingApiError } from "./api-types.js";
|
|
4
|
+
import { ClawlingApiError, } from "./api-types.js";
|
|
5
5
|
import { resolveOpenclawClawlingAccount } from "./config.js";
|
|
6
|
-
import {
|
|
6
|
+
import { clawChatDbPathForStateDir, getClawChatStore, } from "./storage.js";
|
|
7
|
+
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListConversationsSchema, ClawchatListMomentsSchema, ClawchatReplyMomentCommentSchema, ClawchatSearchUsersSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatUploadMediaFileSchema, } from "./tools-schema.js";
|
|
7
8
|
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
8
9
|
function jsonResponse(data) {
|
|
9
10
|
return {
|
|
@@ -24,13 +25,6 @@ function apiError(err) {
|
|
|
24
25
|
function validationError(message) {
|
|
25
26
|
return jsonResponse({ error: "validation", message });
|
|
26
27
|
}
|
|
27
|
-
function resolveActivateCode(params) {
|
|
28
|
-
const explicit = typeof params.code === "string" ? params.code.trim() : "";
|
|
29
|
-
if (explicit)
|
|
30
|
-
return explicit;
|
|
31
|
-
const command = typeof params.command === "string" ? params.command.trim() : "";
|
|
32
|
-
return command.match(/\b[A-Z0-9]{6}\b/u)?.[0] ?? "";
|
|
33
|
-
}
|
|
34
28
|
function genericError(err) {
|
|
35
29
|
return jsonResponse({
|
|
36
30
|
error: "unknown",
|
|
@@ -59,62 +53,150 @@ const MIME_BY_EXT = {
|
|
|
59
53
|
".mov": "video/quicktime",
|
|
60
54
|
".webm": "video/webm",
|
|
61
55
|
};
|
|
56
|
+
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.";
|
|
57
|
+
function toolDescription(...parts) {
|
|
58
|
+
return `${parts.join("")} ${DIRECT_TOOL_GUARD}`;
|
|
59
|
+
}
|
|
62
60
|
function inferMimeFromPath(filePath) {
|
|
63
61
|
const ext = path.extname(filePath).toLowerCase();
|
|
64
62
|
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
65
63
|
}
|
|
66
|
-
|
|
64
|
+
function parseTimestamp(value) {
|
|
65
|
+
if (typeof value !== "string")
|
|
66
|
+
return null;
|
|
67
|
+
const parsed = Date.parse(value);
|
|
68
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
69
|
+
}
|
|
70
|
+
function upsertConversationSummaryCache(store, accountId, conversation) {
|
|
71
|
+
if (!store?.upsertConversationSummary)
|
|
72
|
+
return;
|
|
73
|
+
store.upsertConversationSummary({
|
|
74
|
+
platform: "openclaw",
|
|
75
|
+
accountId,
|
|
76
|
+
conversationId: conversation.id,
|
|
77
|
+
conversationType: conversation.type,
|
|
78
|
+
lastSeenAt: parseTimestamp(conversation.updated_at),
|
|
79
|
+
lastRefreshedAt: Date.now(),
|
|
80
|
+
raw: conversation,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function upsertConversationDetailsCache(store, accountId, conversation) {
|
|
84
|
+
if (!store?.upsertConversationDetails)
|
|
85
|
+
return;
|
|
86
|
+
const refreshedAt = Date.now();
|
|
87
|
+
store.upsertConversationDetails({
|
|
88
|
+
platform: "openclaw",
|
|
89
|
+
accountId,
|
|
90
|
+
conversationId: conversation.id,
|
|
91
|
+
conversationType: conversation.type,
|
|
92
|
+
lastSeenAt: parseTimestamp(conversation.updated_at),
|
|
93
|
+
lastRefreshedAt: refreshedAt,
|
|
94
|
+
raw: conversation,
|
|
95
|
+
...(conversation.type === "group"
|
|
96
|
+
? {
|
|
97
|
+
groupProfile: {
|
|
98
|
+
title: conversation.title,
|
|
99
|
+
raw: conversation,
|
|
100
|
+
lastRefreshedAt: refreshedAt,
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
: {}),
|
|
104
|
+
members: conversation.participants.map((participant) => ({
|
|
105
|
+
userId: participant.user_id,
|
|
106
|
+
role: participant.role,
|
|
107
|
+
raw: participant,
|
|
108
|
+
lastSeenAt: parseTimestamp(participant.joined_at),
|
|
109
|
+
})),
|
|
110
|
+
membersComplete: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function isConversationNotFound(err) {
|
|
114
|
+
return err.meta?.status === 404 || err.meta?.code === 404 || err.meta?.code === 40401;
|
|
115
|
+
}
|
|
116
|
+
export function registerOpenclawClawlingTools(api, options = {}) {
|
|
67
117
|
if (!api.config) {
|
|
68
118
|
api.logger.debug?.("openclaw-clawchat: api.config missing; skipping tool registration");
|
|
69
119
|
return;
|
|
70
120
|
}
|
|
71
|
-
api.registerTool({
|
|
72
|
-
name: "clawchat_activate",
|
|
73
|
-
label: "Clawling: Activate (Login with Invite Code)",
|
|
74
|
-
description: "Activate this OpenClaw plugin on ClawChat by exchanging an invite code for a token. " +
|
|
75
|
-
"Invite codes use six uppercase letters/digits, e.g. A1B2C3. " +
|
|
76
|
-
"TRIGGER — invoke this tool whenever the user's message matches ANY of: " +
|
|
77
|
-
"(1) activation intent with an embedded invite code, such as 'activate ClawChat with invite code A1B2C3', " +
|
|
78
|
-
"'login to ClawChat with invite code A1B2C3', 'connect ClawChat using invite code A1B2C3', " +
|
|
79
|
-
"or '绑定 ClawChat,邀请码 A1B2C3' — call this tool with `code = \"A1B2C3\"`; " +
|
|
80
|
-
"(2) generic activation intent without an embedded code, such as 'activate ClawChat' or " +
|
|
81
|
-
"'login to ClawChat' — ask for invite code before calling the tool; " +
|
|
82
|
-
"(3) activation intent with an embedded code, such as 'use invite code A1B2C3', " +
|
|
83
|
-
"or the user pasting an invite code in the context of ClawChat activation. " +
|
|
84
|
-
"Extract the code verbatim — do NOT normalize / lowercase / add prefixes. " +
|
|
85
|
-
"On success the tool persists the resulting token + userId to the config, so " +
|
|
86
|
-
"subsequent `clawchat_*` calls work without another plugin registration pass.",
|
|
87
|
-
parameters: ClawchatActivateSchema,
|
|
88
|
-
async execute(_callId, params) {
|
|
89
|
-
const code = resolveActivateCode(params);
|
|
90
|
-
if (!code) {
|
|
91
|
-
return validationError("openclaw-clawchat: code is required");
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
const { runOpenclawClawlingLogin } = await import("./login.runtime.js");
|
|
95
|
-
await runOpenclawClawlingLogin({
|
|
96
|
-
cfg: api.config,
|
|
97
|
-
accountId: null,
|
|
98
|
-
runtime: { log: (message) => api.logger.info?.(message) },
|
|
99
|
-
readInviteCode: async () => code,
|
|
100
|
-
mutateConfigFile: api.runtime.config.mutateConfigFile,
|
|
101
|
-
});
|
|
102
|
-
return jsonResponse({
|
|
103
|
-
ok: true,
|
|
104
|
-
message: "ClawChat activated successfully.",
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
if (err instanceof ClawlingApiError)
|
|
109
|
-
return apiError(err);
|
|
110
|
-
return genericError(err);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
}, { name: "clawchat_activate" });
|
|
114
121
|
// Re-resolve at call time so config reloads pick up new tokens / baseUrl.
|
|
115
122
|
function resolveCurrent() {
|
|
116
123
|
return resolveOpenclawClawlingAccount(api.config);
|
|
117
124
|
}
|
|
125
|
+
function resolveStore() {
|
|
126
|
+
if ("store" in options)
|
|
127
|
+
return options.store ?? null;
|
|
128
|
+
try {
|
|
129
|
+
const stateDir = api.runtime.state?.resolveStateDir?.();
|
|
130
|
+
return getClawChatStore({
|
|
131
|
+
...(stateDir ? { dbPath: clawChatDbPathForStateDir(stateDir) } : {}),
|
|
132
|
+
log: { error: (message) => api.logger.error?.(message) },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
api.logger.error?.("openclaw-clawchat sqlite tool persistence unavailable; continuing");
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function detailsError(details) {
|
|
141
|
+
if (!details || typeof details !== "object")
|
|
142
|
+
return null;
|
|
143
|
+
const raw = details;
|
|
144
|
+
if (typeof raw.error !== "string" || !raw.error)
|
|
145
|
+
return null;
|
|
146
|
+
return typeof raw.message === "string" && raw.message
|
|
147
|
+
? `${raw.error}: ${raw.message}`
|
|
148
|
+
: raw.error;
|
|
149
|
+
}
|
|
150
|
+
function persistToolCall(input) {
|
|
151
|
+
try {
|
|
152
|
+
resolveStore()?.recordToolCall(input);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
api.logger.error?.("openclaw-clawchat sqlite tool call insert failed; continuing");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function persistConversationCache(fn) {
|
|
159
|
+
try {
|
|
160
|
+
const store = resolveStore();
|
|
161
|
+
if (store)
|
|
162
|
+
fn(store);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
api.logger.error?.("openclaw-clawchat sqlite conversation cache update failed; continuing");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function recordClawchatToolCall(toolName, params, fn) {
|
|
169
|
+
const startedAt = Date.now();
|
|
170
|
+
const account = resolveCurrent();
|
|
171
|
+
try {
|
|
172
|
+
const result = await fn();
|
|
173
|
+
const details = result.details ?? result;
|
|
174
|
+
persistToolCall({
|
|
175
|
+
platform: "openclaw",
|
|
176
|
+
accountId: account.accountId,
|
|
177
|
+
toolName,
|
|
178
|
+
args: params ?? {},
|
|
179
|
+
result: details,
|
|
180
|
+
error: detailsError(details),
|
|
181
|
+
startedAt,
|
|
182
|
+
endedAt: Date.now(),
|
|
183
|
+
});
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
persistToolCall({
|
|
188
|
+
platform: "openclaw",
|
|
189
|
+
accountId: account.accountId,
|
|
190
|
+
toolName,
|
|
191
|
+
args: params ?? {},
|
|
192
|
+
result: null,
|
|
193
|
+
error: err instanceof Error ? err.message : String(err),
|
|
194
|
+
startedAt,
|
|
195
|
+
endedAt: Date.now(),
|
|
196
|
+
});
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
118
200
|
function buildClient() {
|
|
119
201
|
const acct = resolveCurrent();
|
|
120
202
|
// `baseUrl` always resolves via the built-in default in config.ts, so we
|
|
@@ -151,137 +233,342 @@ export function registerOpenclawClawlingTools(api) {
|
|
|
151
233
|
api.registerTool({
|
|
152
234
|
name: "clawchat_get_account_profile",
|
|
153
235
|
label: "Get ClawChat Account Profile",
|
|
154
|
-
description: "Fetch the
|
|
155
|
-
"
|
|
236
|
+
description: toolDescription("Fetch the agent's connected ClawChat account profile (the configured ClawChat account: user id, nickname/display name, avatar, bio). " +
|
|
237
|
+
"This profile is the platform-side mirror of the local assistant identity; if fields are missing, report them as unset instead of inventing values. " +
|
|
238
|
+
"TRIGGER — invoke when the user asks for the ClawChat account/profile connected to this agent, " +
|
|
156
239
|
"such as 'show my ClawChat profile', 'what is the configured ClawChat account?', " +
|
|
157
240
|
"'当前 ClawChat 账号资料', or 'ClawChat 昵称头像简介'. " +
|
|
158
|
-
"Do not
|
|
241
|
+
"Do not frame this as a human user's personal account."),
|
|
159
242
|
parameters: ClawchatGetAccountProfileSchema,
|
|
160
|
-
async execute(_callId,
|
|
161
|
-
return await withClient((c) => c.getMyProfile());
|
|
243
|
+
async execute(_callId, params) {
|
|
244
|
+
return await recordClawchatToolCall("clawchat_get_account_profile", params, async () => withClient((c) => c.getMyProfile()));
|
|
162
245
|
},
|
|
163
246
|
}, { name: "clawchat_get_account_profile" });
|
|
164
247
|
api.registerTool({
|
|
165
248
|
name: "clawchat_get_user_profile",
|
|
166
249
|
label: "Get ClawChat User Profile",
|
|
167
|
-
description: "Fetch a ClawChat user's public profile by userId. " +
|
|
250
|
+
description: toolDescription("Fetch a ClawChat user's public profile by userId. " +
|
|
168
251
|
"TRIGGER — invoke when the user asks to look up, view, or inspect a specific ClawChat user's public profile " +
|
|
169
|
-
"and provides a concrete userId. Do not guess or infer userId from a nickname/display name."
|
|
252
|
+
"and provides a concrete userId. Do not guess or infer userId from a nickname/display name. " +
|
|
253
|
+
"Use `clawchat_get_account_profile` for the agent's own connected ClawChat account unless an explicit userId is provided."),
|
|
170
254
|
parameters: ClawchatGetUserProfileSchema,
|
|
171
255
|
async execute(_callId, params) {
|
|
172
|
-
|
|
173
|
-
|
|
256
|
+
return await recordClawchatToolCall("clawchat_get_user_profile", params, async () => {
|
|
257
|
+
const p = params;
|
|
258
|
+
return await withClient((c) => c.getUserInfo(p.userId));
|
|
259
|
+
});
|
|
174
260
|
},
|
|
175
261
|
}, { name: "clawchat_get_user_profile" });
|
|
176
262
|
api.registerTool({
|
|
177
263
|
name: "clawchat_list_account_friends",
|
|
178
264
|
label: "List ClawChat Account Friends",
|
|
179
|
-
description: "List
|
|
180
|
-
"
|
|
181
|
-
"
|
|
265
|
+
description: toolDescription("List friends/contacts of the agent's connected ClawChat account (the configured ClawChat account). " +
|
|
266
|
+
"These are the agent's ClawChat-platform contacts. " +
|
|
267
|
+
"TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, or friend list."),
|
|
182
268
|
parameters: ClawchatListAccountFriendsSchema,
|
|
183
269
|
async execute(_callId, params) {
|
|
184
|
-
|
|
185
|
-
return await withClient((c) => c.listFriends({
|
|
186
|
-
...(p.page !== undefined ? { page: p.page } : { page: 1 }),
|
|
187
|
-
...(p.pageSize !== undefined ? { pageSize: p.pageSize } : { pageSize: 20 }),
|
|
188
|
-
}));
|
|
270
|
+
return await recordClawchatToolCall("clawchat_list_account_friends", params, async () => withClient((c) => c.listFriends()));
|
|
189
271
|
},
|
|
190
272
|
}, { name: "clawchat_list_account_friends" });
|
|
273
|
+
api.registerTool({
|
|
274
|
+
name: "clawchat_search_users",
|
|
275
|
+
label: "Search ClawChat Users",
|
|
276
|
+
description: toolDescription("Search ClawChat users by username or nickname. " +
|
|
277
|
+
"TRIGGER - invoke when the user asks to search, find, or look up ClawChat users by a typed query, name, username, or nickname, such as \"search ClawChat users named Alice\", \"查找用户 Alice\", or \"搜一下昵称 Alice\". " +
|
|
278
|
+
"Empty q returns no users. Use this tool before fetching a profile when the user only provides a nickname or search term; do not guess a userId from the query text."),
|
|
279
|
+
parameters: ClawchatSearchUsersSchema,
|
|
280
|
+
async execute(_callId, params) {
|
|
281
|
+
return await recordClawchatToolCall("clawchat_search_users", params, async () => {
|
|
282
|
+
const p = (params ?? {});
|
|
283
|
+
return await withClient((c) => c.searchUsers({
|
|
284
|
+
...(typeof p.q === "string" ? { q: p.q } : {}),
|
|
285
|
+
...(typeof p.limit === "number" ? { limit: p.limit } : {}),
|
|
286
|
+
}));
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
}, { name: "clawchat_search_users" });
|
|
290
|
+
api.registerTool({
|
|
291
|
+
name: "clawchat_list_conversations",
|
|
292
|
+
label: "List ClawChat Conversations",
|
|
293
|
+
description: toolDescription("List ClawChat direct and group conversations visible to the configured account. " +
|
|
294
|
+
"TRIGGER - invoke when the user asks to list, browse, inspect, or paginate ClawChat conversations, chats, groups, or direct messages. " +
|
|
295
|
+
"This is read-only and does not create, update, leave, dissolve, or administer conversations."),
|
|
296
|
+
parameters: ClawchatListConversationsSchema,
|
|
297
|
+
async execute(_callId, params) {
|
|
298
|
+
return await recordClawchatToolCall("clawchat_list_conversations", params, async () => {
|
|
299
|
+
const p = (params ?? {});
|
|
300
|
+
const account = resolveCurrent();
|
|
301
|
+
return await withClient(async (c) => {
|
|
302
|
+
const data = await c.listConversations({
|
|
303
|
+
...(typeof p.before === "string" ? { before: p.before } : {}),
|
|
304
|
+
...(typeof p.limit === "number" ? { limit: p.limit } : {}),
|
|
305
|
+
});
|
|
306
|
+
for (const conversation of data.conversations) {
|
|
307
|
+
persistConversationCache((store) => upsertConversationSummaryCache(store, account.accountId, conversation));
|
|
308
|
+
}
|
|
309
|
+
return data;
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
},
|
|
313
|
+
}, { name: "clawchat_list_conversations" });
|
|
314
|
+
api.registerTool({
|
|
315
|
+
name: "clawchat_get_conversation",
|
|
316
|
+
label: "Get ClawChat Conversation",
|
|
317
|
+
description: toolDescription("Fetch read-only ClawChat conversation details, including group membership when returned by the API. " +
|
|
318
|
+
"TRIGGER - invoke when the user asks to inspect a specific ClawChat conversation or group and provides a concrete conversationId. " +
|
|
319
|
+
"This is read-only and does not create, update, leave, dissolve, or administer conversations."),
|
|
320
|
+
parameters: ClawchatGetConversationSchema,
|
|
321
|
+
async execute(_callId, params) {
|
|
322
|
+
return await recordClawchatToolCall("clawchat_get_conversation", params, async () => {
|
|
323
|
+
const p = params;
|
|
324
|
+
const account = resolveCurrent();
|
|
325
|
+
const built = buildClient();
|
|
326
|
+
if (!built.ok)
|
|
327
|
+
return built.error;
|
|
328
|
+
try {
|
|
329
|
+
const data = await built.client.getConversation(p.conversationId);
|
|
330
|
+
persistConversationCache((store) => upsertConversationDetailsCache(store, account.accountId, data.conversation));
|
|
331
|
+
return jsonResponse(data);
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
if (err instanceof ClawlingApiError) {
|
|
335
|
+
if (isConversationNotFound(err)) {
|
|
336
|
+
persistConversationCache((store) => store.deleteConversationCache?.({
|
|
337
|
+
platform: "openclaw",
|
|
338
|
+
accountId: account.accountId,
|
|
339
|
+
conversationId: p.conversationId,
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
return apiError(err);
|
|
343
|
+
}
|
|
344
|
+
return genericError(err);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
}, { name: "clawchat_get_conversation" });
|
|
349
|
+
api.registerTool({
|
|
350
|
+
name: "clawchat_list_moments",
|
|
351
|
+
label: "List ClawChat Moments",
|
|
352
|
+
description: toolDescription("List the configured ClawChat account's visible moments feed, including moments from the account and its friends. " +
|
|
353
|
+
"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\". " +
|
|
354
|
+
"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."),
|
|
355
|
+
parameters: ClawchatListMomentsSchema,
|
|
356
|
+
async execute(_callId, params) {
|
|
357
|
+
return await recordClawchatToolCall("clawchat_list_moments", params, async () => {
|
|
358
|
+
const p = (params ?? {});
|
|
359
|
+
return await withClient((c) => c.listMoments({
|
|
360
|
+
...(typeof p.before === "number" ? { before: p.before } : {}),
|
|
361
|
+
...(typeof p.limit === "number" ? { limit: p.limit } : {}),
|
|
362
|
+
}));
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
}, { name: "clawchat_list_moments" });
|
|
366
|
+
api.registerTool({
|
|
367
|
+
name: "clawchat_create_moment",
|
|
368
|
+
label: "Create ClawChat Moment",
|
|
369
|
+
description: toolDescription("Create a new ClawChat moment/dynamic for the configured ClawChat account. " +
|
|
370
|
+
"TRIGGER - invoke when the user asks to publish, post, or send a ClawChat moment/dynamic, such as \"post a ClawChat moment saying ...\", \"发布动态 ...\", or \"发朋友圈 ...\". " +
|
|
371
|
+
"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."),
|
|
372
|
+
parameters: ClawchatCreateMomentSchema,
|
|
373
|
+
async execute(_callId, params) {
|
|
374
|
+
return await recordClawchatToolCall("clawchat_create_moment", params, async () => {
|
|
375
|
+
const p = (params ?? {});
|
|
376
|
+
const text = typeof p.text === "string" ? p.text : undefined;
|
|
377
|
+
const images = Array.isArray(p.images) ? p.images : undefined;
|
|
378
|
+
if (!text && (!images || images.length === 0)) {
|
|
379
|
+
return validationError("openclaw-clawchat: at least one of text or images is required");
|
|
380
|
+
}
|
|
381
|
+
return await withClient((c) => c.createMoment({
|
|
382
|
+
...(text !== undefined ? { text } : {}),
|
|
383
|
+
...(images !== undefined ? { images } : {}),
|
|
384
|
+
}));
|
|
385
|
+
});
|
|
386
|
+
},
|
|
387
|
+
}, { name: "clawchat_create_moment" });
|
|
388
|
+
api.registerTool({
|
|
389
|
+
name: "clawchat_delete_moment",
|
|
390
|
+
label: "Delete ClawChat Moment",
|
|
391
|
+
description: toolDescription("Delete a ClawChat moment by moment id. " +
|
|
392
|
+
"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. " +
|
|
393
|
+
"Only the moment author can delete it. Do not guess the id; list moments first if the user refers to a moment ambiguously."),
|
|
394
|
+
parameters: ClawchatDeleteMomentSchema,
|
|
395
|
+
async execute(_callId, params) {
|
|
396
|
+
return await recordClawchatToolCall("clawchat_delete_moment", params, async () => {
|
|
397
|
+
const p = params;
|
|
398
|
+
return await withClient((c) => c.deleteMoment(p.momentId));
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
}, { name: "clawchat_delete_moment" });
|
|
402
|
+
api.registerTool({
|
|
403
|
+
name: "clawchat_toggle_moment_reaction",
|
|
404
|
+
label: "Toggle ClawChat Moment Reaction",
|
|
405
|
+
description: toolDescription("Toggle an emoji reaction on a ClawChat moment. " +
|
|
406
|
+
"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 \"取消这个 👍 反应\". " +
|
|
407
|
+
"The API adds the reaction if missing and removes it if already present. Require a concrete moment id and emoji."),
|
|
408
|
+
parameters: ClawchatToggleMomentReactionSchema,
|
|
409
|
+
async execute(_callId, params) {
|
|
410
|
+
return await recordClawchatToolCall("clawchat_toggle_moment_reaction", params, async () => {
|
|
411
|
+
const p = params;
|
|
412
|
+
if (!p.emoji?.trim()) {
|
|
413
|
+
return validationError("openclaw-clawchat: emoji is required");
|
|
414
|
+
}
|
|
415
|
+
return await withClient((c) => c.toggleMomentReaction({ momentId: p.momentId, emoji: p.emoji }));
|
|
416
|
+
});
|
|
417
|
+
},
|
|
418
|
+
}, { name: "clawchat_toggle_moment_reaction" });
|
|
419
|
+
api.registerTool({
|
|
420
|
+
name: "clawchat_create_moment_comment",
|
|
421
|
+
label: "Create ClawChat Moment Comment",
|
|
422
|
+
description: toolDescription("Create a top-level comment on a ClawChat moment. " +
|
|
423
|
+
"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 \"在这条动态下留言 ...\". " +
|
|
424
|
+
"Require a concrete moment id and non-empty text. Use clawchat_reply_moment_comment when the user is replying to another user's comment."),
|
|
425
|
+
parameters: ClawchatCreateMomentCommentSchema,
|
|
426
|
+
async execute(_callId, params) {
|
|
427
|
+
return await recordClawchatToolCall("clawchat_create_moment_comment", params, async () => {
|
|
428
|
+
const p = params;
|
|
429
|
+
if (!p.text?.trim()) {
|
|
430
|
+
return validationError("openclaw-clawchat: text is required");
|
|
431
|
+
}
|
|
432
|
+
return await withClient((c) => c.createMomentComment({ momentId: p.momentId, text: p.text }));
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
}, { name: "clawchat_create_moment_comment" });
|
|
436
|
+
api.registerTool({
|
|
437
|
+
name: "clawchat_reply_moment_comment",
|
|
438
|
+
label: "Reply To ClawChat Moment Comment",
|
|
439
|
+
description: toolDescription("Reply to an existing ClawChat moment comment with a single-level reply. " +
|
|
440
|
+
"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 \"回复他那条评论 ...\". " +
|
|
441
|
+
"Require concrete moment and comment ids; do not use this for top-level comments."),
|
|
442
|
+
parameters: ClawchatReplyMomentCommentSchema,
|
|
443
|
+
async execute(_callId, params) {
|
|
444
|
+
return await recordClawchatToolCall("clawchat_reply_moment_comment", params, async () => {
|
|
445
|
+
const p = params;
|
|
446
|
+
if (!p.text?.trim()) {
|
|
447
|
+
return validationError("openclaw-clawchat: text is required");
|
|
448
|
+
}
|
|
449
|
+
return await withClient((c) => c.replyMomentComment({
|
|
450
|
+
momentId: p.momentId,
|
|
451
|
+
replyToCommentId: p.replyToCommentId,
|
|
452
|
+
text: p.text,
|
|
453
|
+
}));
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
}, { name: "clawchat_reply_moment_comment" });
|
|
457
|
+
api.registerTool({
|
|
458
|
+
name: "clawchat_delete_moment_comment",
|
|
459
|
+
label: "Delete ClawChat Moment Comment",
|
|
460
|
+
description: toolDescription("Delete a comment on a ClawChat moment. " +
|
|
461
|
+
"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. " +
|
|
462
|
+
"The caller may delete comments they authored or comments on moments they authored. Do not guess ids; list moments first if needed."),
|
|
463
|
+
parameters: ClawchatDeleteMomentCommentSchema,
|
|
464
|
+
async execute(_callId, params) {
|
|
465
|
+
return await recordClawchatToolCall("clawchat_delete_moment_comment", params, async () => {
|
|
466
|
+
const p = params;
|
|
467
|
+
return await withClient((c) => c.deleteMomentComment({ momentId: p.momentId, commentId: p.commentId }));
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
}, { name: "clawchat_delete_moment_comment" });
|
|
191
471
|
api.registerTool({
|
|
192
472
|
name: "clawchat_update_account_profile",
|
|
193
473
|
label: "Update ClawChat Account Profile",
|
|
194
|
-
description: "Update the configured ClawChat account
|
|
195
|
-
"TRIGGER — invoke this tool whenever the user's message
|
|
474
|
+
description: toolDescription("Update nickname/avatar_url/bio on the agent's connected ClawChat account (the configured ClawChat account), which mirrors the local assistant identity. " +
|
|
475
|
+
"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: " +
|
|
196
476
|
"(1) ClawChat account nickname/name change: 'change the ClawChat account nickname to X', " +
|
|
197
|
-
"'set this
|
|
477
|
+
"'set this assistant name to X', 'ClawChat 昵称改为 X', '账号昵称改成 X', '账号名字叫 X' " +
|
|
198
478
|
"→ call with `nickname = X`; " +
|
|
199
479
|
"(2) ClawChat account avatar/profile-picture change: 'change the ClawChat account avatar', " +
|
|
200
|
-
"'use this image as the
|
|
480
|
+
"'use this image as the assistant profile picture', 'ClawChat 头像改为 …', '账号头像换成 …' " +
|
|
201
481
|
"→ first obtain the avatar URL (upload via `clawchat_upload_avatar_image`, OR use a provided URL directly), " +
|
|
202
482
|
"then call this tool with `avatar_url = <url>`; " +
|
|
203
483
|
"(3) ClawChat account bio/self-introduction change: 'update the ClawChat bio', " +
|
|
204
|
-
"'set the
|
|
484
|
+
"'set the assistant self-introduction to X', 'ClawChat 简介改成 X', '账号简介改为 X', '个人简介改为 X' " +
|
|
205
485
|
"→ call with `bio = X`. " +
|
|
206
486
|
"You can pass `nickname`, `avatar_url`, and `bio` together in one call, or just one of them. " +
|
|
207
|
-
"At least one of the three must be present. Do not
|
|
487
|
+
"At least one of the three must be present. Do not frame this as updating a human user's personal account."),
|
|
208
488
|
parameters: ClawchatUpdateAccountProfileSchema,
|
|
209
489
|
async execute(_callId, params) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
490
|
+
return await recordClawchatToolCall("clawchat_update_account_profile", params, async () => {
|
|
491
|
+
const p = (params ?? {});
|
|
492
|
+
const patch = {};
|
|
493
|
+
if (typeof p.nickname === "string")
|
|
494
|
+
patch.nickname = p.nickname;
|
|
495
|
+
if (typeof p.avatar_url === "string")
|
|
496
|
+
patch.avatar_url = p.avatar_url;
|
|
497
|
+
if (typeof p.bio === "string")
|
|
498
|
+
patch.bio = p.bio;
|
|
499
|
+
if (Object.keys(patch).length === 0) {
|
|
500
|
+
return validationError("openclaw-clawchat: at least one of nickname / avatar / bio is required");
|
|
501
|
+
}
|
|
502
|
+
return await withClient((c) => c.updateMyProfile(patch));
|
|
503
|
+
});
|
|
222
504
|
},
|
|
223
505
|
}, { name: "clawchat_update_account_profile" });
|
|
224
506
|
api.registerTool({
|
|
225
507
|
name: "clawchat_upload_avatar_image",
|
|
226
508
|
label: "Upload ClawChat Avatar Image",
|
|
227
|
-
description: "Upload
|
|
509
|
+
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. " +
|
|
228
510
|
"TRIGGER — invoke when the user provides an absolute local image path and asks to upload it for the ClawChat account avatar/profile picture. " +
|
|
229
|
-
"This tool does not update or set the account avatar by itself; call `clawchat_update_account_profile` with `avatar_url` after this tool returns a URL.",
|
|
511
|
+
"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."),
|
|
230
512
|
parameters: ClawchatUploadAvatarImageSchema,
|
|
231
513
|
async execute(_callId, params) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
514
|
+
return await recordClawchatToolCall("clawchat_upload_avatar_image", params, async () => {
|
|
515
|
+
const p = params;
|
|
516
|
+
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
517
|
+
return validationError("openclaw-clawchat: filePath must be an absolute local path");
|
|
518
|
+
}
|
|
519
|
+
let stat;
|
|
520
|
+
try {
|
|
521
|
+
stat = fs.statSync(p.filePath);
|
|
522
|
+
}
|
|
523
|
+
catch (err) {
|
|
524
|
+
return validationError(`openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
525
|
+
}
|
|
526
|
+
if (!stat.isFile()) {
|
|
527
|
+
return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
|
|
528
|
+
}
|
|
529
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
530
|
+
return validationError(`openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`);
|
|
531
|
+
}
|
|
532
|
+
const buffer = fs.readFileSync(p.filePath);
|
|
533
|
+
const filename = path.basename(p.filePath);
|
|
534
|
+
const mime = inferMimeFromPath(p.filePath);
|
|
535
|
+
return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
|
|
536
|
+
});
|
|
253
537
|
},
|
|
254
538
|
}, { name: "clawchat_upload_avatar_image" });
|
|
255
539
|
api.registerTool({
|
|
256
540
|
name: "clawchat_upload_media_file",
|
|
257
541
|
label: "Upload ClawChat Media File",
|
|
258
|
-
description: "Upload
|
|
542
|
+
description: toolDescription("Upload an absolute local file/media path to ClawChat media storage (max 20MB) and return a ClawChat-accessible public/shareable URL. " +
|
|
259
543
|
"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. " +
|
|
260
|
-
"Do not use this
|
|
544
|
+
"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). " +
|
|
545
|
+
"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."),
|
|
261
546
|
parameters: ClawchatUploadMediaFileSchema,
|
|
262
547
|
async execute(_callId, params) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
548
|
+
return await recordClawchatToolCall("clawchat_upload_media_file", params, async () => {
|
|
549
|
+
const p = params;
|
|
550
|
+
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
551
|
+
return validationError("openclaw-clawchat: filePath must be an absolute local path");
|
|
552
|
+
}
|
|
553
|
+
let stat;
|
|
554
|
+
try {
|
|
555
|
+
stat = fs.statSync(p.filePath);
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
return validationError(`openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
559
|
+
}
|
|
560
|
+
if (!stat.isFile()) {
|
|
561
|
+
return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
|
|
562
|
+
}
|
|
563
|
+
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
564
|
+
return validationError(`openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`);
|
|
565
|
+
}
|
|
566
|
+
const buffer = fs.readFileSync(p.filePath);
|
|
567
|
+
const filename = path.basename(p.filePath);
|
|
568
|
+
const mime = inferMimeFromPath(p.filePath);
|
|
569
|
+
return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
|
|
570
|
+
});
|
|
284
571
|
},
|
|
285
572
|
}, { name: "clawchat_upload_media_file" });
|
|
286
|
-
api.logger.debug?.("openclaw-clawchat: registered
|
|
573
|
+
api.logger.debug?.("openclaw-clawchat: registered 16 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, search_users, list_conversations, get_conversation, 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)");
|
|
287
574
|
}
|