@newbase-clawchat/openclaw-clawchat 2026.4.24 → 2026.4.29
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/README.md +40 -13
- package/index.ts +2 -1
- package/openclaw.plugin.json +69 -1
- package/package.json +3 -11
- package/skills/clawchat-account-tools/SKILL.md +26 -0
- package/skills/clawchat-activate/SKILL.md +38 -0
- package/src/api-client.test.ts +6 -5
- package/src/api-client.ts +8 -3
- package/src/buffered-stream.test.ts +4 -4
- package/src/buffered-stream.ts +16 -8
- package/src/channel.outbound.test.ts +49 -35
- package/src/channel.test.ts +45 -10
- package/src/channel.ts +15 -14
- package/src/client.test.ts +2 -1
- package/src/client.ts +37 -11
- package/src/commands.test.ts +33 -0
- package/src/commands.ts +37 -0
- package/src/config.test.ts +37 -3
- package/src/config.ts +53 -4
- package/src/inbound.test.ts +5 -5
- package/src/inbound.ts +43 -9
- package/src/login.runtime.test.ts +106 -3
- package/src/login.runtime.ts +8 -3
- package/src/manifest.test.ts +106 -4
- package/src/outbound.test.ts +7 -5
- package/src/plugin-entry.test.ts +27 -0
- package/src/protocol.ts +5 -0
- package/src/reply-dispatcher.test.ts +4 -2
- package/src/runtime.test.ts +23 -7
- package/src/runtime.ts +12 -1
- package/src/streaming.test.ts +3 -3
- package/src/streaming.ts +19 -9
- package/src/tools-schema.ts +28 -19
- package/src/tools.test.ts +115 -37
- package/src/tools.ts +137 -116
package/src/tools.test.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { registerOpenclawClawlingTools } from "./tools.ts";
|
|
3
3
|
|
|
4
|
+
const loginRuntime = vi.hoisted(() => ({
|
|
5
|
+
runOpenclawClawlingLogin: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock("./login.runtime.ts", () => loginRuntime);
|
|
9
|
+
|
|
4
10
|
interface RegisteredTool {
|
|
5
11
|
name: string;
|
|
6
12
|
execute: (callId: string, params: unknown) => Promise<unknown>;
|
|
@@ -8,6 +14,7 @@ interface RegisteredTool {
|
|
|
8
14
|
|
|
9
15
|
function buildApi(opts: {
|
|
10
16
|
configChannel?: Record<string, unknown> | null;
|
|
17
|
+
configTools?: Record<string, unknown>;
|
|
11
18
|
registerTool?: (tool: { name: string }, options?: { name: string }) => void;
|
|
12
19
|
}) {
|
|
13
20
|
const registered: RegisteredTool[] = [];
|
|
@@ -15,7 +22,10 @@ function buildApi(opts: {
|
|
|
15
22
|
config:
|
|
16
23
|
opts.configChannel === null
|
|
17
24
|
? undefined
|
|
18
|
-
: {
|
|
25
|
+
: {
|
|
26
|
+
channels: { "openclaw-clawchat": opts.configChannel ?? {} },
|
|
27
|
+
...(opts.configTools ? { tools: opts.configTools } : {}),
|
|
28
|
+
},
|
|
19
29
|
logger: {
|
|
20
30
|
info: vi.fn(),
|
|
21
31
|
debug: vi.fn(),
|
|
@@ -39,14 +49,38 @@ function configuredChannel(extra: Record<string, unknown> = {}) {
|
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
describe("registerOpenclawClawlingTools", () => {
|
|
42
|
-
it("registers
|
|
52
|
+
it("registers no tools when account.configured is false", () => {
|
|
53
|
+
const { api, registered } = buildApi({
|
|
54
|
+
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
55
|
+
});
|
|
56
|
+
registerOpenclawClawlingTools(api);
|
|
57
|
+
expect(registered.map((t) => t.name)).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not mutate tool policy during registration before account activation", () => {
|
|
61
|
+
const { api, registered } = buildApi({
|
|
62
|
+
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
63
|
+
configTools: { profile: "coding", allow: [] },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
registerOpenclawClawlingTools(api);
|
|
67
|
+
|
|
68
|
+
expect(registered.map((t) => t.name)).toEqual([]);
|
|
69
|
+
expect(api.config?.tools).toEqual({
|
|
70
|
+
profile: "coding",
|
|
71
|
+
allow: [],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("does not register clawchat_activate for invite-code onboarding", async () => {
|
|
43
76
|
const { api, registered } = buildApi({
|
|
44
77
|
configChannel: { websocketUrl: "wss://w" /* token / userId missing */ },
|
|
45
78
|
});
|
|
79
|
+
|
|
46
80
|
registerOpenclawClawlingTools(api);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
expect(
|
|
81
|
+
|
|
82
|
+
expect(registered.some((t) => t.name === "clawchat_activate")).toBe(false);
|
|
83
|
+
expect(loginRuntime.runOpenclawClawlingLogin).not.toHaveBeenCalled();
|
|
50
84
|
});
|
|
51
85
|
|
|
52
86
|
it("skips registration when api.config is undefined", () => {
|
|
@@ -55,62 +89,106 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
55
89
|
expect(registered).toHaveLength(0);
|
|
56
90
|
});
|
|
57
91
|
|
|
58
|
-
it("registers all
|
|
92
|
+
it("registers all six account tools when configured (regardless of baseUrl)", () => {
|
|
59
93
|
const { api, registered } = buildApi({
|
|
60
94
|
configChannel: configuredChannel(/* no baseUrl */),
|
|
61
95
|
});
|
|
62
96
|
registerOpenclawClawlingTools(api);
|
|
63
97
|
const names = registered.map((t) => t.name).sort();
|
|
64
98
|
expect(names).toEqual([
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"clawchat_upload_file",
|
|
99
|
+
"clawchat_get_account_profile",
|
|
100
|
+
"clawchat_get_user_profile",
|
|
101
|
+
"clawchat_list_account_friends",
|
|
102
|
+
"clawchat_update_account_profile",
|
|
103
|
+
"clawchat_upload_avatar_image",
|
|
104
|
+
"clawchat_upload_media_file",
|
|
72
105
|
]);
|
|
73
106
|
});
|
|
74
107
|
|
|
75
|
-
it("
|
|
108
|
+
it("logs configured tool registration at debug level only", () => {
|
|
109
|
+
const { api } = buildApi({
|
|
110
|
+
configChannel: configuredChannel(),
|
|
111
|
+
});
|
|
112
|
+
const logger = api.logger as {
|
|
113
|
+
info: ReturnType<typeof vi.fn>;
|
|
114
|
+
debug: ReturnType<typeof vi.fn>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
registerOpenclawClawlingTools(api);
|
|
118
|
+
|
|
119
|
+
expect(logger.info).not.toHaveBeenCalled();
|
|
120
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
121
|
+
"openclaw-clawchat: registered 6 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, update_account_profile, upload_avatar_image, upload_media_file)",
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("does not register clawchat_activate when configured", () => {
|
|
76
126
|
const { api, registered } = buildApi({
|
|
77
127
|
configChannel: configuredChannel(),
|
|
78
128
|
});
|
|
79
|
-
|
|
129
|
+
registerOpenclawClawlingTools(api);
|
|
130
|
+
expect(registered.some((t) => t.name === "clawchat_activate")).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("clawchat_update_account_profile description names account profile triggers (EN + ZH)", () => {
|
|
134
|
+
const { api } = buildApi({ configChannel: configuredChannel() });
|
|
80
135
|
const fullTools: Array<{ name: string; description?: string }> = [];
|
|
81
136
|
api.registerTool = (tool: { name: string; description?: string }) => {
|
|
82
137
|
fullTools.push(tool);
|
|
83
138
|
};
|
|
84
139
|
registerOpenclawClawlingTools(api);
|
|
85
|
-
const
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
expect(
|
|
89
|
-
expect(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect(
|
|
140
|
+
const update = fullTools.find((t) => t.name === "clawchat_update_account_profile")!;
|
|
141
|
+
expect(update).toBeDefined();
|
|
142
|
+
expect(update.description).toMatch(/configured ClawChat account|logged-in ClawChat account/i);
|
|
143
|
+
expect(update.description).toMatch(/ClawChat (account )?(nickname|name)/i);
|
|
144
|
+
expect(update.description).toMatch(/ClawChat 昵称|账号昵称|账号名字/);
|
|
145
|
+
expect(update.description).toMatch(/avatar|profile picture/i);
|
|
146
|
+
expect(update.description).toMatch(/ClawChat 头像|账号头像/);
|
|
147
|
+
expect(update.description).toMatch(/clawchat_upload_avatar_image/);
|
|
148
|
+
expect(update.description).toMatch(/bio|self-introduction/i);
|
|
149
|
+
expect(update.description).toMatch(/ClawChat 简介|账号简介|个人简介/);
|
|
150
|
+
expect(update.description).not.toMatch(
|
|
151
|
+
new RegExp(["agent's own", "rename " + "yourself", "this agent's own"].join("|"), "i"),
|
|
152
|
+
);
|
|
93
153
|
});
|
|
94
154
|
|
|
95
|
-
it("
|
|
155
|
+
it("account query and upload tool descriptions include precise trigger semantics", () => {
|
|
96
156
|
const { api } = buildApi({ configChannel: configuredChannel() });
|
|
97
157
|
const fullTools: Array<{ name: string; description?: string }> = [];
|
|
98
158
|
api.registerTool = (tool: { name: string; description?: string }) => {
|
|
99
159
|
fullTools.push(tool);
|
|
100
160
|
};
|
|
101
161
|
registerOpenclawClawlingTools(api);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
expect(
|
|
110
|
-
expect(
|
|
162
|
+
|
|
163
|
+
const accountProfile = fullTools.find((t) => t.name === "clawchat_get_account_profile")!;
|
|
164
|
+
const userProfile = fullTools.find((t) => t.name === "clawchat_get_user_profile")!;
|
|
165
|
+
const friends = fullTools.find((t) => t.name === "clawchat_list_account_friends")!;
|
|
166
|
+
const avatar = fullTools.find((t) => t.name === "clawchat_upload_avatar_image")!;
|
|
167
|
+
const media = fullTools.find((t) => t.name === "clawchat_upload_media_file")!;
|
|
168
|
+
|
|
169
|
+
expect(accountProfile.description).toMatch(/configured ClawChat account|logged-in ClawChat account/i);
|
|
170
|
+
expect(accountProfile.description).toMatch(/profile|nickname|avatar|bio/i);
|
|
171
|
+
expect(accountProfile.description).not.toMatch(/agent's own|this agent's/i);
|
|
172
|
+
|
|
173
|
+
expect(userProfile.description).toMatch(/userId/);
|
|
174
|
+
expect(userProfile.description).toMatch(/public profile/i);
|
|
175
|
+
expect(userProfile.description).toMatch(/do not guess|do not infer/i);
|
|
176
|
+
|
|
177
|
+
expect(friends.description).toMatch(/configured ClawChat account|logged-in ClawChat account/i);
|
|
178
|
+
expect(friends.description).toMatch(/friends|contacts/i);
|
|
179
|
+
expect(friends.description).toMatch(/page=1|pageSize=20/);
|
|
180
|
+
|
|
181
|
+
expect(avatar.description).toMatch(/local image/i);
|
|
182
|
+
expect(avatar.description).toMatch(/avatar URL|hosted avatar URL|public URL/i);
|
|
183
|
+
expect(avatar.description).toMatch(/clawchat_update_account_profile/);
|
|
184
|
+
expect(avatar.description).toMatch(/does not update|does not set/i);
|
|
185
|
+
|
|
186
|
+
expect(media.description).toMatch(/local file|media file/i);
|
|
187
|
+
expect(media.description).toMatch(/public URL|shareable URL/i);
|
|
188
|
+
expect(media.description).toMatch(/not.*avatar|do not use.*avatar/i);
|
|
111
189
|
});
|
|
112
190
|
|
|
113
|
-
it("
|
|
191
|
+
it("clawchat_upload_avatar_image rejects oversized files before upload", async () => {
|
|
114
192
|
const fs = await import("node:fs/promises");
|
|
115
193
|
const path = await import("node:path");
|
|
116
194
|
const os = await import("node:os");
|
|
@@ -124,7 +202,7 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
124
202
|
configChannel: configuredChannel({ baseUrl: "https://api.example.com" }),
|
|
125
203
|
});
|
|
126
204
|
registerOpenclawClawlingTools(api);
|
|
127
|
-
const tool = registered.find((t) => t.name === "
|
|
205
|
+
const tool = registered.find((t) => t.name === "clawchat_upload_avatar_image")!;
|
|
128
206
|
const result = await tool.execute("call-1", { filePath: big });
|
|
129
207
|
const text = (result as { content: { text: string }[] }).content[0]!.text;
|
|
130
208
|
const parsed = JSON.parse(text) as { error?: string; message?: string };
|
|
@@ -136,7 +214,7 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
136
214
|
});
|
|
137
215
|
|
|
138
216
|
|
|
139
|
-
it("
|
|
217
|
+
it("clawchat_upload_media_file rejects oversized files before upload", async () => {
|
|
140
218
|
const fs = await import("node:fs/promises");
|
|
141
219
|
const path = await import("node:path");
|
|
142
220
|
const os = await import("node:os");
|
|
@@ -151,7 +229,7 @@ describe("registerOpenclawClawlingTools", () => {
|
|
|
151
229
|
configChannel: configuredChannel({ baseUrl: "https://api.example.com" }),
|
|
152
230
|
});
|
|
153
231
|
registerOpenclawClawlingTools(api);
|
|
154
|
-
const tool = registered.find((t) => t.name === "
|
|
232
|
+
const tool = registered.find((t) => t.name === "clawchat_upload_media_file")!;
|
|
155
233
|
const result = await tool.execute("call-1", { filePath: big });
|
|
156
234
|
const text = (result as { content: { text: string }[] }).content[0]!.text;
|
|
157
235
|
const parsed = JSON.parse(text) as { error?: string; message?: string };
|
package/src/tools.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type {
|
|
3
|
+
import type { OpenClawAgentToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
4
4
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
|
|
5
5
|
import { createOpenclawClawlingApiClient } from "./api-client.ts";
|
|
6
6
|
import { ClawlingApiError, type Profile } from "./api-types.ts";
|
|
7
7
|
import { resolveOpenclawClawlingAccount } from "./config.ts";
|
|
8
8
|
import {
|
|
9
|
-
ClawchatActivateSchema,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type ClawchatActivateParams,
|
|
17
|
-
type
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
9
|
+
// ClawchatActivateSchema,
|
|
10
|
+
ClawchatGetAccountProfileSchema,
|
|
11
|
+
ClawchatGetUserProfileSchema,
|
|
12
|
+
ClawchatListAccountFriendsSchema,
|
|
13
|
+
ClawchatUpdateAccountProfileSchema,
|
|
14
|
+
ClawchatUploadAvatarImageSchema,
|
|
15
|
+
ClawchatUploadMediaFileSchema,
|
|
16
|
+
// type ClawchatActivateParams,
|
|
17
|
+
type ClawchatGetUserProfileParams,
|
|
18
|
+
type ClawchatListAccountFriendsParams,
|
|
19
|
+
type ClawchatUpdateAccountProfileParams,
|
|
20
|
+
type ClawchatUploadAvatarImageParams,
|
|
21
|
+
type ClawchatUploadMediaFileParams,
|
|
22
22
|
} from "./tools-schema.ts";
|
|
23
23
|
|
|
24
24
|
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
25
25
|
|
|
26
|
-
function jsonResponse(data: unknown):
|
|
26
|
+
function jsonResponse(data: unknown): OpenClawAgentToolResult<unknown> {
|
|
27
27
|
return {
|
|
28
28
|
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
29
29
|
details: data,
|
|
@@ -46,6 +46,13 @@ function validationError(message: string) {
|
|
|
46
46
|
return jsonResponse({ error: "validation", message });
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// function resolveActivateCode(params: ClawchatActivateParams & { command?: unknown }): string {
|
|
50
|
+
// const explicit = typeof params.code === "string" ? params.code.trim() : "";
|
|
51
|
+
// if (explicit) return explicit;
|
|
52
|
+
// const command = typeof params.command === "string" ? params.command.trim() : "";
|
|
53
|
+
// return command.match(/\b[A-Z0-9]{6}\b/u)?.[0] ?? "";
|
|
54
|
+
// }
|
|
55
|
+
|
|
49
56
|
function genericError(err: unknown) {
|
|
50
57
|
return jsonResponse({
|
|
51
58
|
error: "unknown",
|
|
@@ -88,59 +95,60 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
88
95
|
}
|
|
89
96
|
|
|
90
97
|
// -----------------------------------------------------------------------
|
|
91
|
-
// `clawchat_activate`
|
|
92
|
-
//
|
|
93
|
-
// `account.configured === false`. All other tools below require a valid
|
|
94
|
-
// token and are gated on `account.configured`.
|
|
98
|
+
// `clawchat_activate` tool disabled: activation now goes through OpenClaw
|
|
99
|
+
// channel login.
|
|
95
100
|
// -----------------------------------------------------------------------
|
|
96
|
-
api.registerTool(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
101
|
+
// api.registerTool(
|
|
102
|
+
// {
|
|
103
|
+
// name: "clawchat_activate",
|
|
104
|
+
// label: "Clawling: Activate (Login with Invite Code)",
|
|
105
|
+
// description:
|
|
106
|
+
// "Activate this OpenClaw plugin on ClawChat by exchanging an invite code for a token. " +
|
|
107
|
+
// "Invite codes use six uppercase letters/digits, e.g. A1B2C3. " +
|
|
108
|
+
// "TRIGGER — invoke this tool whenever the user's message matches ANY of: " +
|
|
109
|
+
// "(1) activation intent with an embedded invite code, such as 'activate ClawChat with invite code A1B2C3', " +
|
|
110
|
+
// "'login to ClawChat with invite code A1B2C3', 'connect ClawChat using invite code A1B2C3', " +
|
|
111
|
+
// "or '绑定 ClawChat,邀请码 A1B2C3' — call this tool with `code = \"A1B2C3\"`; " +
|
|
112
|
+
// "(2) generic activation intent without an embedded code, such as 'activate ClawChat' or " +
|
|
113
|
+
// "'login to ClawChat' — ask for invite code before calling the tool; " +
|
|
114
|
+
// "(3) activation intent with an embedded code, such as 'use invite code A1B2C3', " +
|
|
115
|
+
// "or the user pasting an invite code in the context of ClawChat activation. " +
|
|
116
|
+
// "Do not ask the user to enter a bare ClawChat command; if an invite code is present, call this tool directly. " +
|
|
117
|
+
// "Extract the code verbatim — do NOT normalize / lowercase / add prefixes. " +
|
|
118
|
+
// "On success the tool persists the resulting token + userId to the config, so " +
|
|
119
|
+
// "subsequent `clawchat_*` calls work without any other setup.",
|
|
120
|
+
// parameters: ClawchatActivateSchema,
|
|
121
|
+
// async execute(_callId, params) {
|
|
122
|
+
// const code = resolveActivateCode(params as ClawchatActivateParams & { command?: unknown });
|
|
123
|
+
// if (!code) {
|
|
124
|
+
// return validationError("openclaw-clawchat: code is required");
|
|
125
|
+
// }
|
|
126
|
+
// try {
|
|
127
|
+
// const { runOpenclawClawlingLogin } = await import("./login.runtime.ts");
|
|
128
|
+
// await runOpenclawClawlingLogin({
|
|
129
|
+
// cfg: api.config!,
|
|
130
|
+
// accountId: null,
|
|
131
|
+
// runtime: { log: (message: string) => api.logger.info?.(message) },
|
|
132
|
+
// readInviteCode: async () => code,
|
|
133
|
+
// });
|
|
134
|
+
// return jsonResponse({
|
|
135
|
+
// ok: true,
|
|
136
|
+
// message: "✅ ClawChat activated successfully.",
|
|
137
|
+
// });
|
|
138
|
+
// } catch (err) {
|
|
139
|
+
// if (err instanceof ClawlingApiError) return apiError(err);
|
|
140
|
+
// return genericError(err);
|
|
141
|
+
// }
|
|
142
|
+
// },
|
|
143
|
+
// },
|
|
144
|
+
// { name: "clawchat_activate" },
|
|
145
|
+
// );
|
|
138
146
|
|
|
139
147
|
const account = resolveOpenclawClawlingAccount(api.config);
|
|
140
148
|
if (!account.configured) {
|
|
141
149
|
api.logger.debug?.(
|
|
142
|
-
"openclaw-clawchat: account not yet configured;
|
|
143
|
-
"
|
|
150
|
+
"openclaw-clawchat: account not yet configured; account tools will register " +
|
|
151
|
+
"on the next config reload after channel login.",
|
|
144
152
|
);
|
|
145
153
|
return;
|
|
146
154
|
}
|
|
@@ -151,7 +159,7 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
type ClientResult =
|
|
154
|
-
| { error:
|
|
162
|
+
| { error: OpenClawAgentToolResult<unknown>; client?: never }
|
|
155
163
|
| { client: ReturnType<typeof createOpenclawClawlingApiClient>; error?: never };
|
|
156
164
|
|
|
157
165
|
function buildClient(): ClientResult {
|
|
@@ -173,8 +181,8 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
173
181
|
|
|
174
182
|
function withClient<T>(
|
|
175
183
|
fn: (client: ReturnType<typeof createOpenclawClawlingApiClient>) => Promise<T>,
|
|
176
|
-
): Promise<
|
|
177
|
-
return (async (): Promise<
|
|
184
|
+
): Promise<OpenClawAgentToolResult<unknown>> {
|
|
185
|
+
return (async (): Promise<OpenClawAgentToolResult<unknown>> => {
|
|
178
186
|
const built = buildClient();
|
|
179
187
|
if (built.error !== undefined) return built.error;
|
|
180
188
|
try {
|
|
@@ -189,39 +197,50 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
189
197
|
|
|
190
198
|
api.registerTool(
|
|
191
199
|
{
|
|
192
|
-
name: "
|
|
193
|
-
label: "Get Profile",
|
|
194
|
-
description:
|
|
195
|
-
|
|
200
|
+
name: "clawchat_get_account_profile",
|
|
201
|
+
label: "Get ClawChat Account Profile",
|
|
202
|
+
description:
|
|
203
|
+
"Fetch the configured ClawChat account profile (user id, nickname/display name, avatar, bio). " +
|
|
204
|
+
"TRIGGER — invoke when the user asks for the ClawChat account/profile connected to this plugin, " +
|
|
205
|
+
"such as 'show my ClawChat profile', 'what is the configured ClawChat account?', " +
|
|
206
|
+
"'当前 ClawChat 账号资料', or 'ClawChat 昵称头像简介'. " +
|
|
207
|
+
"Do not use this for OpenClaw agent persona/profile questions unless the user explicitly means the ClawChat account.",
|
|
208
|
+
parameters: ClawchatGetAccountProfileSchema,
|
|
196
209
|
async execute(_callId, _params) {
|
|
197
210
|
return await withClient((c) => c.getMyProfile());
|
|
198
211
|
},
|
|
199
212
|
},
|
|
200
|
-
{ name: "
|
|
213
|
+
{ name: "clawchat_get_account_profile" },
|
|
201
214
|
);
|
|
202
215
|
|
|
203
216
|
api.registerTool(
|
|
204
217
|
{
|
|
205
|
-
name: "
|
|
206
|
-
label: "Get User
|
|
207
|
-
description:
|
|
208
|
-
|
|
218
|
+
name: "clawchat_get_user_profile",
|
|
219
|
+
label: "Get ClawChat User Profile",
|
|
220
|
+
description:
|
|
221
|
+
"Fetch a ClawChat user's public profile by userId. " +
|
|
222
|
+
"TRIGGER — invoke when the user asks to look up, view, or inspect a specific ClawChat user's public profile " +
|
|
223
|
+
"and provides a concrete userId. Do not guess or infer userId from a nickname/display name.",
|
|
224
|
+
parameters: ClawchatGetUserProfileSchema,
|
|
209
225
|
async execute(_callId, params) {
|
|
210
|
-
const p = params as
|
|
226
|
+
const p = params as ClawchatGetUserProfileParams;
|
|
211
227
|
return await withClient((c) => c.getUserInfo(p.userId));
|
|
212
228
|
},
|
|
213
229
|
},
|
|
214
|
-
{ name: "
|
|
230
|
+
{ name: "clawchat_get_user_profile" },
|
|
215
231
|
);
|
|
216
232
|
|
|
217
233
|
api.registerTool(
|
|
218
234
|
{
|
|
219
|
-
name: "
|
|
220
|
-
label: "List Friends",
|
|
221
|
-
description:
|
|
222
|
-
|
|
235
|
+
name: "clawchat_list_account_friends",
|
|
236
|
+
label: "List ClawChat Account Friends",
|
|
237
|
+
description:
|
|
238
|
+
"List the configured ClawChat account's friends/contacts, paginated (page=1, pageSize=20 by default). " +
|
|
239
|
+
"TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, friend list, " +
|
|
240
|
+
"or asks to show more friends with pagination.",
|
|
241
|
+
parameters: ClawchatListAccountFriendsSchema,
|
|
223
242
|
async execute(_callId, params) {
|
|
224
|
-
const p = (params ?? {}) as
|
|
243
|
+
const p = (params ?? {}) as ClawchatListAccountFriendsParams;
|
|
225
244
|
return await withClient((c) =>
|
|
226
245
|
c.listFriends({
|
|
227
246
|
...(p.page !== undefined ? { page: p.page } : { page: 1 }),
|
|
@@ -230,32 +249,31 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
230
249
|
);
|
|
231
250
|
},
|
|
232
251
|
},
|
|
233
|
-
{ name: "
|
|
252
|
+
{ name: "clawchat_list_account_friends" },
|
|
234
253
|
);
|
|
235
254
|
|
|
236
255
|
api.registerTool(
|
|
237
256
|
{
|
|
238
|
-
name: "
|
|
239
|
-
label: "Update Profile",
|
|
257
|
+
name: "clawchat_update_account_profile",
|
|
258
|
+
label: "Update ClawChat Account Profile",
|
|
240
259
|
description:
|
|
241
|
-
"Update
|
|
242
|
-
"TRIGGER — invoke this tool whenever the user's message
|
|
243
|
-
"(1) nickname/name change: 'change
|
|
244
|
-
"'
|
|
245
|
-
"
|
|
246
|
-
"(2) avatar change
|
|
247
|
-
"'
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"'change your self introduction', '把简介改成 X', '更新自我介绍', '个人简介改为 X' " +
|
|
260
|
+
"Update the configured ClawChat account profile (nickname and/or avatar and/or bio). " +
|
|
261
|
+
"TRIGGER — invoke this tool whenever the user's message explicitly asks to change the ClawChat account profile: " +
|
|
262
|
+
"(1) ClawChat account nickname/name change: 'change the ClawChat account nickname to X', " +
|
|
263
|
+
"'set this ClawChat account name to X', 'ClawChat 昵称改为 X', '账号昵称改成 X', '账号名字叫 X' " +
|
|
264
|
+
"→ call with `nickname = X`; " +
|
|
265
|
+
"(2) ClawChat account avatar/profile-picture change: 'change the ClawChat account avatar', " +
|
|
266
|
+
"'use this image as the ClawChat profile picture', 'ClawChat 头像改为 …', '账号头像换成 …' " +
|
|
267
|
+
"→ first obtain the avatar URL (upload via `clawchat_upload_avatar_image`, OR use a provided URL directly), " +
|
|
268
|
+
"then call this tool with `avatar_url = <url>`; " +
|
|
269
|
+
"(3) ClawChat account bio/self-introduction change: 'update the ClawChat bio', " +
|
|
270
|
+
"'set the ClawChat account self-introduction to X', 'ClawChat 简介改成 X', '账号简介改为 X', '个人简介改为 X' " +
|
|
253
271
|
"→ call with `bio = X`. " +
|
|
254
272
|
"You can pass `nickname`, `avatar_url`, and `bio` together in one call, or just one of them. " +
|
|
255
|
-
"At least one of the three must be present.",
|
|
256
|
-
parameters:
|
|
273
|
+
"At least one of the three must be present. Do not use this for OpenClaw agent persona changes unless the user explicitly refers to the ClawChat account.",
|
|
274
|
+
parameters: ClawchatUpdateAccountProfileSchema,
|
|
257
275
|
async execute(_callId, params) {
|
|
258
|
-
const p = (params ?? {}) as
|
|
276
|
+
const p = (params ?? {}) as ClawchatUpdateAccountProfileParams;
|
|
259
277
|
const patch: { nickname?: string; avatar_url?: string; bio?: string } = {};
|
|
260
278
|
if (typeof p.nickname === "string") patch.nickname = p.nickname;
|
|
261
279
|
if (typeof p.avatar_url === "string") patch.avatar_url = p.avatar_url;
|
|
@@ -268,19 +286,20 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
268
286
|
return await withClient((c): Promise<Profile> => c.updateMyProfile(patch));
|
|
269
287
|
},
|
|
270
288
|
},
|
|
271
|
-
{ name: "
|
|
289
|
+
{ name: "clawchat_update_account_profile" },
|
|
272
290
|
);
|
|
273
291
|
|
|
274
292
|
api.registerTool(
|
|
275
293
|
{
|
|
276
|
-
name: "
|
|
277
|
-
label: "Upload Avatar",
|
|
294
|
+
name: "clawchat_upload_avatar_image",
|
|
295
|
+
label: "Upload ClawChat Avatar Image",
|
|
278
296
|
description:
|
|
279
|
-
"Upload a local
|
|
280
|
-
"
|
|
281
|
-
|
|
297
|
+
"Upload a local image file to ClawChat avatar storage (max 20MB) and return the hosted avatar URL. " +
|
|
298
|
+
"TRIGGER — invoke when the user provides an absolute local image path and asks to upload it for the ClawChat account avatar/profile picture. " +
|
|
299
|
+
"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.",
|
|
300
|
+
parameters: ClawchatUploadAvatarImageSchema,
|
|
282
301
|
async execute(_callId, params) {
|
|
283
|
-
const p = params as
|
|
302
|
+
const p = params as ClawchatUploadAvatarImageParams;
|
|
284
303
|
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
285
304
|
return validationError("openclaw-clawchat: filePath must be an absolute local path");
|
|
286
305
|
}
|
|
@@ -306,18 +325,20 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
306
325
|
return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
|
|
307
326
|
},
|
|
308
327
|
},
|
|
309
|
-
{ name: "
|
|
328
|
+
{ name: "clawchat_upload_avatar_image" },
|
|
310
329
|
);
|
|
311
330
|
|
|
312
331
|
api.registerTool(
|
|
313
332
|
{
|
|
314
|
-
name: "
|
|
315
|
-
label: "Upload File",
|
|
333
|
+
name: "clawchat_upload_media_file",
|
|
334
|
+
label: "Upload ClawChat Media File",
|
|
316
335
|
description:
|
|
317
|
-
"Upload a local file to
|
|
318
|
-
|
|
336
|
+
"Upload a local file or media file to ClawChat media storage (max 20MB) and return the public URL/shareable URL. " +
|
|
337
|
+
"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. " +
|
|
338
|
+
"Do not use this for account avatar changes; use `clawchat_upload_avatar_image` for avatar images.",
|
|
339
|
+
parameters: ClawchatUploadMediaFileSchema,
|
|
319
340
|
async execute(_callId, params) {
|
|
320
|
-
const p = params as
|
|
341
|
+
const p = params as ClawchatUploadMediaFileParams;
|
|
321
342
|
if (!p.filePath || !path.isAbsolute(p.filePath)) {
|
|
322
343
|
return validationError("openclaw-clawchat: filePath must be an absolute local path");
|
|
323
344
|
}
|
|
@@ -343,10 +364,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
|
|
|
343
364
|
return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
|
|
344
365
|
},
|
|
345
366
|
},
|
|
346
|
-
{ name: "
|
|
367
|
+
{ name: "clawchat_upload_media_file" },
|
|
347
368
|
);
|
|
348
369
|
|
|
349
|
-
api.logger.
|
|
350
|
-
"openclaw-clawchat: registered
|
|
370
|
+
api.logger.debug?.(
|
|
371
|
+
"openclaw-clawchat: registered 6 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, update_account_profile, upload_avatar_image, upload_media_file)",
|
|
351
372
|
);
|
|
352
373
|
}
|