@newbase-clawchat/openclaw-clawchat 2026.4.24 → 2026.4.30

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.
Files changed (56) hide show
  1. package/README.md +66 -16
  2. package/dist/index.js +27 -0
  3. package/dist/src/api-client.js +156 -0
  4. package/dist/src/api-types.js +17 -0
  5. package/dist/src/buffered-stream.js +177 -0
  6. package/dist/src/channel.js +191 -0
  7. package/dist/src/client.js +176 -0
  8. package/dist/src/commands.js +35 -0
  9. package/dist/src/config.js +214 -0
  10. package/dist/src/inbound.js +133 -0
  11. package/dist/src/login.runtime.js +130 -0
  12. package/dist/src/media-runtime.js +85 -0
  13. package/dist/src/message-mapper.js +82 -0
  14. package/dist/src/outbound.js +181 -0
  15. package/dist/src/protocol.js +38 -0
  16. package/dist/src/reply-dispatcher.js +440 -0
  17. package/dist/src/runtime.js +288 -0
  18. package/dist/src/streaming.js +65 -0
  19. package/dist/src/tools-schema.js +38 -0
  20. package/dist/src/tools.js +287 -0
  21. package/index.ts +2 -1
  22. package/openclaw.plugin.json +81 -1
  23. package/package.json +21 -9
  24. package/skills/clawchat-account-tools/SKILL.md +26 -0
  25. package/skills/clawchat-activate/SKILL.md +47 -0
  26. package/src/api-client.test.ts +6 -5
  27. package/src/api-client.ts +8 -3
  28. package/src/buffered-stream.test.ts +14 -4
  29. package/src/buffered-stream.ts +19 -11
  30. package/src/channel.outbound.test.ts +49 -35
  31. package/src/channel.test.ts +45 -10
  32. package/src/channel.ts +26 -17
  33. package/src/client.test.ts +9 -1
  34. package/src/client.ts +48 -21
  35. package/src/commands.test.ts +39 -0
  36. package/src/commands.ts +41 -0
  37. package/src/config.test.ts +40 -3
  38. package/src/config.ts +60 -4
  39. package/src/inbound.test.ts +9 -6
  40. package/src/inbound.ts +51 -16
  41. package/src/login.runtime.test.ts +142 -3
  42. package/src/login.runtime.ts +59 -26
  43. package/src/manifest.test.ts +183 -5
  44. package/src/outbound.test.ts +10 -7
  45. package/src/outbound.ts +8 -7
  46. package/src/plugin-entry.test.ts +27 -0
  47. package/src/protocol.ts +5 -0
  48. package/src/reply-dispatcher.test.ts +420 -3
  49. package/src/reply-dispatcher.ts +137 -12
  50. package/src/runtime.test.ts +23 -7
  51. package/src/runtime.ts +13 -1
  52. package/src/streaming.test.ts +12 -9
  53. package/src/streaming.ts +22 -12
  54. package/src/tools-schema.ts +28 -19
  55. package/src/tools.test.ts +181 -40
  56. package/src/tools.ts +107 -95
package/src/tools.ts CHANGED
@@ -1,29 +1,30 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import type { AgentToolResult } from "@mariozechner/pi-agent-core";
4
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/feishu";
3
+ import type { OpenClawAgentToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
4
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
5
+ import type { OpenclawClawchatMutateConfigFile } from "./login.runtime.ts";
5
6
  import { createOpenclawClawlingApiClient } from "./api-client.ts";
6
7
  import { ClawlingApiError, type Profile } from "./api-types.ts";
7
8
  import { resolveOpenclawClawlingAccount } from "./config.ts";
8
9
  import {
9
10
  ClawchatActivateSchema,
10
- ClawchatGetMyProfileSchema,
11
- ClawchatGetUserInfoSchema,
12
- ClawchatListFriendsSchema,
13
- ClawchatUpdateMyProfileSchema,
14
- ClawchatUploadAvatarSchema,
15
- ClawchatUploadFileSchema,
11
+ ClawchatGetAccountProfileSchema,
12
+ ClawchatGetUserProfileSchema,
13
+ ClawchatListAccountFriendsSchema,
14
+ ClawchatUpdateAccountProfileSchema,
15
+ ClawchatUploadAvatarImageSchema,
16
+ ClawchatUploadMediaFileSchema,
16
17
  type ClawchatActivateParams,
17
- type ClawchatGetUserInfoParams,
18
- type ClawchatListFriendsParams,
19
- type ClawchatUpdateMyProfileParams,
20
- type ClawchatUploadAvatarParams,
21
- type ClawchatUploadFileParams,
18
+ type ClawchatGetUserProfileParams,
19
+ type ClawchatListAccountFriendsParams,
20
+ type ClawchatUpdateAccountProfileParams,
21
+ type ClawchatUploadAvatarImageParams,
22
+ type ClawchatUploadMediaFileParams,
22
23
  } from "./tools-schema.ts";
23
24
 
24
25
  const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
25
26
 
26
- function jsonResponse(data: unknown): AgentToolResult<unknown> {
27
+ function jsonResponse(data: unknown): OpenClawAgentToolResult<unknown> {
27
28
  return {
28
29
  content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
29
30
  details: data,
@@ -46,6 +47,13 @@ function validationError(message: string) {
46
47
  return jsonResponse({ error: "validation", message });
47
48
  }
48
49
 
50
+ function resolveActivateCode(params: ClawchatActivateParams & { command?: unknown }): string {
51
+ const explicit = typeof params.code === "string" ? params.code.trim() : "";
52
+ if (explicit) return explicit;
53
+ const command = typeof params.command === "string" ? params.command.trim() : "";
54
+ return command.match(/\b[A-Z0-9]{6}\b/u)?.[0] ?? "";
55
+ }
56
+
49
57
  function genericError(err: unknown) {
50
58
  return jsonResponse({
51
59
  error: "unknown",
@@ -87,31 +95,27 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
87
95
  return;
88
96
  }
89
97
 
90
- // -----------------------------------------------------------------------
91
- // `clawchat_activate` is registered UNCONDITIONALLY — it is the tool the
92
- // agent invokes to onboard this plugin, so it must be available even when
93
- // `account.configured === false`. All other tools below require a valid
94
- // token and are gated on `account.configured`.
95
- // -----------------------------------------------------------------------
96
98
  api.registerTool(
97
99
  {
98
100
  name: "clawchat_activate",
99
101
  label: "Clawling: Activate (Login with Invite Code)",
100
102
  description:
101
- "Activate this agent on ClawChat by exchanging an invite code for a token. " +
103
+ "Activate this OpenClaw plugin on ClawChat by exchanging an invite code for a token. " +
104
+ "Invite codes use six uppercase letters/digits, e.g. A1B2C3. " +
102
105
  "TRIGGER — invoke this tool whenever the user's message matches ANY of: " +
103
- "(1) the pattern `clawchat <code>` or `clawchat: <code>` e.g. 'clawchat INV-ABC123' " +
104
- "means: call this tool with `code = \"INV-ABC123\"`; " +
105
- "(2) phrases like 'activate clawchat', 'login to clawchat', 'use invite code XYZ', " +
106
- "'connect clawchat with XYZ'; " +
107
- "(3) the user pastes anything that looks like an invite code (e.g. `INV-…`, `CC-…`). " +
106
+ "(1) activation intent with an embedded invite code, such as 'activate ClawChat with invite code A1B2C3', " +
107
+ "'login to ClawChat with invite code A1B2C3', 'connect ClawChat using invite code A1B2C3', " +
108
+ "or '绑定 ClawChat,邀请码 A1B2C3' call this tool with `code = \"A1B2C3\"`; " +
109
+ "(2) generic activation intent without an embedded code, such as 'activate ClawChat' or " +
110
+ "'login to ClawChat' ask for invite code before calling the tool; " +
111
+ "(3) activation intent with an embedded code, such as 'use invite code A1B2C3', " +
112
+ "or the user pasting an invite code in the context of ClawChat activation. " +
108
113
  "Extract the code verbatim — do NOT normalize / lowercase / add prefixes. " +
109
114
  "On success the tool persists the resulting token + userId to the config, so " +
110
- "subsequent `clawchat_*` calls work without any other setup.",
115
+ "subsequent `clawchat_*` calls work without another plugin registration pass.",
111
116
  parameters: ClawchatActivateSchema,
112
117
  async execute(_callId, params) {
113
- const p = params as ClawchatActivateParams;
114
- const code = p.code?.trim();
118
+ const code = resolveActivateCode(params as ClawchatActivateParams & { command?: unknown });
115
119
  if (!code) {
116
120
  return validationError("openclaw-clawchat: code is required");
117
121
  }
@@ -122,10 +126,13 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
122
126
  accountId: null,
123
127
  runtime: { log: (message: string) => api.logger.info?.(message) },
124
128
  readInviteCode: async () => code,
129
+ mutateConfigFile: (api.runtime.config as unknown as {
130
+ mutateConfigFile: OpenclawClawchatMutateConfigFile;
131
+ }).mutateConfigFile,
125
132
  });
126
133
  return jsonResponse({
127
134
  ok: true,
128
- message: "ClawChat activated successfully.",
135
+ message: "ClawChat activated successfully.",
129
136
  });
130
137
  } catch (err) {
131
138
  if (err instanceof ClawlingApiError) return apiError(err);
@@ -136,23 +143,14 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
136
143
  { name: "clawchat_activate" },
137
144
  );
138
145
 
139
- const account = resolveOpenclawClawlingAccount(api.config);
140
- if (!account.configured) {
141
- api.logger.debug?.(
142
- "openclaw-clawchat: account not yet configured; only clawchat_activate is registered. " +
143
- "The remaining tools will register on the next config reload after activation.",
144
- );
145
- return;
146
- }
147
-
148
146
  // Re-resolve at call time so config reloads pick up new tokens / baseUrl.
149
147
  function resolveCurrent() {
150
148
  return resolveOpenclawClawlingAccount(api.config!);
151
149
  }
152
150
 
153
151
  type ClientResult =
154
- | { error: AgentToolResult<unknown>; client?: never }
155
- | { client: ReturnType<typeof createOpenclawClawlingApiClient>; error?: never };
152
+ | { ok: false; error: OpenClawAgentToolResult<unknown> }
153
+ | { ok: true; client: ReturnType<typeof createOpenclawClawlingApiClient> };
156
154
 
157
155
  function buildClient(): ClientResult {
158
156
  const acct = resolveCurrent();
@@ -160,9 +158,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
160
158
  // only need to gate on `token` here (which is populated by `openclaw
161
159
  // channel login`).
162
160
  if (!acct.token) {
163
- return { error: configError("openclaw-clawchat: token is required") };
161
+ return { ok: false, error: configError("openclaw-clawchat: token is required") };
164
162
  }
165
163
  return {
164
+ ok: true,
166
165
  client: createOpenclawClawlingApiClient({
167
166
  baseUrl: acct.baseUrl,
168
167
  token: acct.token,
@@ -173,10 +172,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
173
172
 
174
173
  function withClient<T>(
175
174
  fn: (client: ReturnType<typeof createOpenclawClawlingApiClient>) => Promise<T>,
176
- ): Promise<AgentToolResult<unknown>> {
177
- return (async (): Promise<AgentToolResult<unknown>> => {
175
+ ): Promise<OpenClawAgentToolResult<unknown>> {
176
+ return (async (): Promise<OpenClawAgentToolResult<unknown>> => {
178
177
  const built = buildClient();
179
- if (built.error !== undefined) return built.error;
178
+ if (!built.ok) return built.error;
180
179
  try {
181
180
  const data = await fn(built.client);
182
181
  return jsonResponse(data);
@@ -189,39 +188,50 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
189
188
 
190
189
  api.registerTool(
191
190
  {
192
- name: "clawchat_get_my_profile",
193
- label: "Get Profile",
194
- description: "Fetch the agent's own Clawling profile (id, display name, avatar, bio).",
195
- parameters: ClawchatGetMyProfileSchema,
191
+ name: "clawchat_get_account_profile",
192
+ label: "Get ClawChat Account Profile",
193
+ description:
194
+ "Fetch the configured ClawChat account profile (user id, nickname/display name, avatar, bio). " +
195
+ "TRIGGER — invoke when the user asks for the ClawChat account/profile connected to this plugin, " +
196
+ "such as 'show my ClawChat profile', 'what is the configured ClawChat account?', " +
197
+ "'当前 ClawChat 账号资料', or 'ClawChat 昵称头像简介'. " +
198
+ "Do not use this for OpenClaw agent persona/profile questions unless the user explicitly means the ClawChat account.",
199
+ parameters: ClawchatGetAccountProfileSchema,
196
200
  async execute(_callId, _params) {
197
201
  return await withClient((c) => c.getMyProfile());
198
202
  },
199
203
  },
200
- { name: "clawchat_get_my_profile" },
204
+ { name: "clawchat_get_account_profile" },
201
205
  );
202
206
 
203
207
  api.registerTool(
204
208
  {
205
- name: "clawchat_get_user_info",
206
- label: "Get User Info",
207
- description: "Fetch a Clawling user's public profile by userId.",
208
- parameters: ClawchatGetUserInfoSchema,
209
+ name: "clawchat_get_user_profile",
210
+ label: "Get ClawChat User Profile",
211
+ description:
212
+ "Fetch a ClawChat user's public profile by userId. " +
213
+ "TRIGGER — invoke when the user asks to look up, view, or inspect a specific ClawChat user's public profile " +
214
+ "and provides a concrete userId. Do not guess or infer userId from a nickname/display name.",
215
+ parameters: ClawchatGetUserProfileSchema,
209
216
  async execute(_callId, params) {
210
- const p = params as ClawchatGetUserInfoParams;
217
+ const p = params as ClawchatGetUserProfileParams;
211
218
  return await withClient((c) => c.getUserInfo(p.userId));
212
219
  },
213
220
  },
214
- { name: "clawchat_get_user_info" },
221
+ { name: "clawchat_get_user_profile" },
215
222
  );
216
223
 
217
224
  api.registerTool(
218
225
  {
219
- name: "clawchat_list_friends",
220
- label: "List Friends",
221
- description: "List the agent's friends, paginated (page=1, pageSize=20 by default).",
222
- parameters: ClawchatListFriendsSchema,
226
+ name: "clawchat_list_account_friends",
227
+ label: "List ClawChat Account Friends",
228
+ description:
229
+ "List the configured ClawChat account's friends/contacts, paginated (page=1, pageSize=20 by default). " +
230
+ "TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, friend list, " +
231
+ "or asks to show more friends with pagination.",
232
+ parameters: ClawchatListAccountFriendsSchema,
223
233
  async execute(_callId, params) {
224
- const p = (params ?? {}) as ClawchatListFriendsParams;
234
+ const p = (params ?? {}) as ClawchatListAccountFriendsParams;
225
235
  return await withClient((c) =>
226
236
  c.listFriends({
227
237
  ...(p.page !== undefined ? { page: p.page } : { page: 1 }),
@@ -230,32 +240,31 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
230
240
  );
231
241
  },
232
242
  },
233
- { name: "clawchat_list_friends" },
243
+ { name: "clawchat_list_account_friends" },
234
244
  );
235
245
 
236
246
  api.registerTool(
237
247
  {
238
- name: "clawchat_update_my_profile",
239
- label: "Update Profile",
248
+ name: "clawchat_update_account_profile",
249
+ label: "Update ClawChat Account Profile",
240
250
  description:
241
- "Update this agent's own ClawChat profile (nickname and/or avatar and/or bio). " +
242
- "TRIGGER — invoke this tool whenever the user's message matches ANY of: " +
243
- "(1) nickname/name change: 'change your name to X', 'your name is X', 'rename yourself to X', " +
244
- "'I'll call you X', 'from now on you are X', '你叫 X', '改名为 X', '我叫你 X', " +
245
- "'你的新名字是 X' → call with `nickname = X`; " +
246
- "(2) avatar change or generation: 'change your avatar', 'update your profile picture', " +
247
- "'generate a new avatar', 'use this image as your avatar', '换个头像', '生成头像', " +
248
- "'把头像改为 …' → first obtain the avatar URL (generate + upload via " +
249
- "`clawchat_upload_avatar`, OR use a provided URL directly), then call this tool " +
250
- "with `avatar_url = <url>`; " +
251
- "(3) bio/self-introduction change: 'update your bio', 'set your profile bio to X', " +
252
- "'change your self introduction', '把简介改成 X', '更新自我介绍', '个人简介改为 X' " +
251
+ "Update the configured ClawChat account profile (nickname and/or avatar and/or bio). " +
252
+ "TRIGGER — invoke this tool whenever the user's message explicitly asks to change the ClawChat account profile: " +
253
+ "(1) ClawChat account nickname/name change: 'change the ClawChat account nickname to X', " +
254
+ "'set this ClawChat account name to X', 'ClawChat 昵称改为 X', '账号昵称改成 X', '账号名字叫 X' " +
255
+ "→ call with `nickname = X`; " +
256
+ "(2) ClawChat account avatar/profile-picture change: 'change the ClawChat account avatar', " +
257
+ "'use this image as the ClawChat profile picture', 'ClawChat 头像改为 …', '账号头像换成 …' " +
258
+ "→ first obtain the avatar URL (upload via `clawchat_upload_avatar_image`, OR use a provided URL directly), " +
259
+ "then call this tool with `avatar_url = <url>`; " +
260
+ "(3) ClawChat account bio/self-introduction change: 'update the ClawChat bio', " +
261
+ "'set the ClawChat account self-introduction to X', 'ClawChat 简介改成 X', '账号简介改为 X', '个人简介改为 X' " +
253
262
  "→ call with `bio = X`. " +
254
263
  "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: ClawchatUpdateMyProfileSchema,
264
+ "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.",
265
+ parameters: ClawchatUpdateAccountProfileSchema,
257
266
  async execute(_callId, params) {
258
- const p = (params ?? {}) as ClawchatUpdateMyProfileParams;
267
+ const p = (params ?? {}) as ClawchatUpdateAccountProfileParams;
259
268
  const patch: { nickname?: string; avatar_url?: string; bio?: string } = {};
260
269
  if (typeof p.nickname === "string") patch.nickname = p.nickname;
261
270
  if (typeof p.avatar_url === "string") patch.avatar_url = p.avatar_url;
@@ -268,19 +277,20 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
268
277
  return await withClient((c): Promise<Profile> => c.updateMyProfile(patch));
269
278
  },
270
279
  },
271
- { name: "clawchat_update_my_profile" },
280
+ { name: "clawchat_update_account_profile" },
272
281
  );
273
282
 
274
283
  api.registerTool(
275
284
  {
276
- name: "clawchat_upload_avatar",
277
- label: "Upload Avatar",
285
+ name: "clawchat_upload_avatar_image",
286
+ label: "Upload ClawChat Avatar Image",
278
287
  description:
279
- "Upload a local avatar image to Clawling avatar storage (max 20MB) and return the public URL. " +
280
- "Use this before `clawchat_update_my_profile` when changing the profile picture.",
281
- parameters: ClawchatUploadAvatarSchema,
288
+ "Upload a local image file to ClawChat avatar storage (max 20MB) and return the hosted avatar URL. " +
289
+ "TRIGGER invoke when the user provides an absolute local image path and asks to upload it for the ClawChat account avatar/profile picture. " +
290
+ "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.",
291
+ parameters: ClawchatUploadAvatarImageSchema,
282
292
  async execute(_callId, params) {
283
- const p = params as ClawchatUploadAvatarParams;
293
+ const p = params as ClawchatUploadAvatarImageParams;
284
294
  if (!p.filePath || !path.isAbsolute(p.filePath)) {
285
295
  return validationError("openclaw-clawchat: filePath must be an absolute local path");
286
296
  }
@@ -306,18 +316,20 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
306
316
  return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
307
317
  },
308
318
  },
309
- { name: "clawchat_upload_avatar" },
319
+ { name: "clawchat_upload_avatar_image" },
310
320
  );
311
321
 
312
322
  api.registerTool(
313
323
  {
314
- name: "clawchat_upload_file",
315
- label: "Upload File",
324
+ name: "clawchat_upload_media_file",
325
+ label: "Upload ClawChat Media File",
316
326
  description:
317
- "Upload a local file to Clawling media storage (max 20MB) and return the public URL.",
318
- parameters: ClawchatUploadFileSchema,
327
+ "Upload a local file or media file to ClawChat media storage (max 20MB) and return the public URL/shareable URL. " +
328
+ "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. " +
329
+ "Do not use this for account avatar changes; use `clawchat_upload_avatar_image` for avatar images.",
330
+ parameters: ClawchatUploadMediaFileSchema,
319
331
  async execute(_callId, params) {
320
- const p = params as ClawchatUploadFileParams;
332
+ const p = params as ClawchatUploadMediaFileParams;
321
333
  if (!p.filePath || !path.isAbsolute(p.filePath)) {
322
334
  return validationError("openclaw-clawchat: filePath must be an absolute local path");
323
335
  }
@@ -343,10 +355,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
343
355
  return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
344
356
  },
345
357
  },
346
- { name: "clawchat_upload_file" },
358
+ { name: "clawchat_upload_media_file" },
347
359
  );
348
360
 
349
- api.logger.info?.(
350
- "openclaw-clawchat: registered 7 clawchat_* tools (activate, get_my_profile, get_user_info, list_friends, update_my_profile, upload_avatar, upload_file)",
361
+ api.logger.debug?.(
362
+ "openclaw-clawchat: registered 7 clawchat_* tools (activate, get_account_profile, get_user_profile, list_account_friends, update_account_profile, upload_avatar_image, upload_media_file)",
351
363
  );
352
364
  }