@newbase-clawchat/openclaw-clawchat 2026.5.12-2 → 2026.5.12-21

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 (99) hide show
  1. package/README.md +39 -17
  2. package/dist/index.js +3 -1
  3. package/dist/src/api-client.js +71 -12
  4. package/dist/src/api-types.test-d.js +10 -0
  5. package/dist/src/channel.js +5 -5
  6. package/dist/src/channel.setup.js +4 -17
  7. package/dist/src/clawchat-memory.js +290 -0
  8. package/dist/src/clawchat-metadata.js +235 -0
  9. package/dist/src/client.js +31 -93
  10. package/dist/src/commands.js +3 -3
  11. package/dist/src/config.js +58 -3
  12. package/dist/src/group-message-coalescer.js +107 -0
  13. package/dist/src/inbound.js +24 -28
  14. package/dist/src/login.runtime.js +82 -19
  15. package/dist/src/media-runtime.js +2 -3
  16. package/dist/src/message-mapper.js +1 -1
  17. package/dist/src/mock-transport.js +31 -0
  18. package/dist/src/outbound.js +281 -56
  19. package/dist/src/plugin-prompts.js +76 -0
  20. package/dist/src/profile-prompt.js +150 -0
  21. package/dist/src/profile-sync.js +169 -0
  22. package/dist/src/prompt-injection.js +25 -0
  23. package/dist/src/protocol-types.js +63 -0
  24. package/dist/src/protocol-types.typecheck.js +1 -0
  25. package/dist/src/protocol.js +2 -2
  26. package/dist/src/reply-dispatcher.js +143 -40
  27. package/dist/src/runtime.js +813 -109
  28. package/dist/src/storage.js +636 -0
  29. package/dist/src/tools-schema.js +70 -10
  30. package/dist/src/tools.js +600 -112
  31. package/dist/src/ws-alignment.js +8 -0
  32. package/dist/src/ws-client.js +588 -0
  33. package/index.ts +6 -1
  34. package/openclaw.plugin.json +44 -4
  35. package/package.json +4 -3
  36. package/prompts/platform.md +7 -0
  37. package/skills/clawchat/SKILL.md +90 -0
  38. package/src/api-client.test.ts +360 -15
  39. package/src/api-client.ts +127 -25
  40. package/src/api-types.test-d.ts +12 -0
  41. package/src/api-types.ts +71 -4
  42. package/src/buffered-stream.test.ts +1 -1
  43. package/src/buffered-stream.ts +1 -1
  44. package/src/channel.outbound.test.ts +270 -60
  45. package/src/channel.setup.ts +9 -18
  46. package/src/channel.test.ts +33 -25
  47. package/src/channel.ts +5 -7
  48. package/src/clawchat-memory.test.ts +372 -0
  49. package/src/clawchat-memory.ts +363 -0
  50. package/src/clawchat-metadata.test.ts +350 -0
  51. package/src/clawchat-metadata.ts +352 -0
  52. package/src/client.test.ts +57 -48
  53. package/src/client.ts +37 -129
  54. package/src/commands.test.ts +2 -2
  55. package/src/commands.ts +3 -3
  56. package/src/config.test.ts +169 -4
  57. package/src/config.ts +86 -6
  58. package/src/group-message-coalescer.test.ts +223 -0
  59. package/src/group-message-coalescer.ts +154 -0
  60. package/src/inbound.test.ts +106 -19
  61. package/src/inbound.ts +31 -35
  62. package/src/login.runtime.test.ts +294 -11
  63. package/src/login.runtime.ts +90 -21
  64. package/src/manifest.test.ts +86 -14
  65. package/src/media-runtime.test.ts +31 -2
  66. package/src/media-runtime.ts +7 -10
  67. package/src/message-mapper.test.ts +2 -2
  68. package/src/message-mapper.ts +2 -2
  69. package/src/mock-transport.test.ts +35 -0
  70. package/src/mock-transport.ts +38 -0
  71. package/src/outbound.test.ts +811 -95
  72. package/src/outbound.ts +332 -65
  73. package/src/plugin-entry.test.ts +3 -1
  74. package/src/plugin-prompts.test.ts +78 -0
  75. package/src/plugin-prompts.ts +92 -0
  76. package/src/profile-prompt.test.ts +435 -0
  77. package/src/profile-prompt.ts +208 -0
  78. package/src/profile-sync.test.ts +611 -0
  79. package/src/profile-sync.ts +268 -0
  80. package/src/prompt-injection.test.ts +39 -0
  81. package/src/prompt-injection.ts +45 -0
  82. package/src/protocol-types.test.ts +69 -0
  83. package/src/protocol-types.ts +296 -0
  84. package/src/protocol-types.typecheck.ts +89 -0
  85. package/src/protocol.ts +2 -2
  86. package/src/reply-dispatcher.test.ts +720 -135
  87. package/src/reply-dispatcher.ts +174 -42
  88. package/src/runtime.test.ts +3884 -337
  89. package/src/runtime.ts +956 -128
  90. package/src/storage.test.ts +692 -0
  91. package/src/storage.ts +989 -0
  92. package/src/streaming.test.ts +1 -1
  93. package/src/streaming.ts +1 -1
  94. package/src/tools-schema.ts +115 -13
  95. package/src/tools.test.ts +501 -10
  96. package/src/tools.ts +739 -133
  97. package/src/ws-alignment.ts +9 -0
  98. package/src/ws-client.test.ts +1218 -0
  99. package/src/ws-client.ts +662 -0
package/src/tools.ts CHANGED
@@ -1,18 +1,49 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import type { OpenClawAgentToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
4
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
4
+ import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
5
5
  import { createOpenclawClawlingApiClient } from "./api-client.ts";
6
- import { ClawlingApiError, type Profile } from "./api-types.ts";
6
+ import {
7
+ ClawlingApiError,
8
+ type ConversationDetails,
9
+ type ConversationListItem,
10
+ type Profile,
11
+ } from "./api-types.ts";
7
12
  import { resolveOpenclawClawlingAccount } from "./config.ts";
13
+ import {
14
+ clawChatDbPathForStateDir,
15
+ getClawChatStore,
16
+ type ClawChatStore,
17
+ } from "./storage.ts";
18
+ import {
19
+ editClawChatMemoryBody,
20
+ readClawChatMemoryFile,
21
+ resolveClawChatMemoryPath,
22
+ writeClawChatMemoryBody,
23
+ type ClawChatMemoryTargetType,
24
+ } from "./clawchat-memory.ts";
25
+ import {
26
+ pullGroupMetadata,
27
+ pullOwnerMetadata,
28
+ pullUserMetadata,
29
+ pushMetadata,
30
+ updateMetadata,
31
+ } from "./clawchat-metadata.ts";
8
32
  import {
9
33
  ClawchatGetAccountProfileSchema,
34
+ ClawchatGetConversationSchema,
10
35
  ClawchatGetUserProfileSchema,
36
+ ClawchatMemoryEditSchema,
37
+ ClawchatMemoryReadSchema,
38
+ ClawchatMemoryWriteSchema,
39
+ ClawchatMetadataSyncSchema,
40
+ ClawchatMetadataUpdateSchema,
11
41
  ClawchatCreateMomentCommentSchema,
12
42
  ClawchatCreateMomentSchema,
13
43
  ClawchatDeleteMomentCommentSchema,
14
44
  ClawchatDeleteMomentSchema,
15
45
  ClawchatListAccountFriendsSchema,
46
+ ClawchatListConversationsSchema,
16
47
  ClawchatListMomentsSchema,
17
48
  ClawchatReplyMomentCommentSchema,
18
49
  ClawchatSearchUsersSchema,
@@ -20,12 +51,18 @@ import {
20
51
  ClawchatUpdateAccountProfileSchema,
21
52
  ClawchatUploadAvatarImageSchema,
22
53
  ClawchatUploadMediaFileSchema,
54
+ type ClawchatMemoryEditParams,
55
+ type ClawchatMemoryReadParams,
56
+ type ClawchatMemoryWriteParams,
57
+ type ClawchatMetadataSyncParams,
58
+ type ClawchatMetadataUpdateParams,
23
59
  type ClawchatCreateMomentCommentParams,
24
60
  type ClawchatCreateMomentParams,
25
61
  type ClawchatDeleteMomentCommentParams,
26
62
  type ClawchatDeleteMomentParams,
63
+ type ClawchatGetConversationParams,
27
64
  type ClawchatGetUserProfileParams,
28
- type ClawchatListAccountFriendsParams,
65
+ type ClawchatListConversationsParams,
29
66
  type ClawchatListMomentsParams,
30
67
  type ClawchatReplyMomentCommentParams,
31
68
  type ClawchatSearchUsersParams,
@@ -93,16 +130,89 @@ const MIME_BY_EXT: Record<string, string> = {
93
130
  const DIRECT_TOOL_GUARD =
94
131
  "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.";
95
132
 
133
+ const MEMORY_READ_DEFAULT_LIMIT = 20_000;
134
+ const MEMORY_READ_MAX_LIMIT = 100_000;
135
+
136
+ type ToolStore = Pick<
137
+ ClawChatStore,
138
+ "recordToolCall"
139
+ > &
140
+ Partial<
141
+ Pick<
142
+ ClawChatStore,
143
+ "upsertConversationSummary" | "upsertConversationDetails" | "deleteConversationCache"
144
+ >
145
+ >;
146
+
96
147
  function toolDescription(...parts: string[]): string {
97
148
  return `${parts.join("")} ${DIRECT_TOOL_GUARD}`;
98
149
  }
99
150
 
151
+ function memoryToolDescription(...parts: string[]): string {
152
+ return parts.join("");
153
+ }
154
+
100
155
  function inferMimeFromPath(filePath: string): string {
101
156
  const ext = path.extname(filePath).toLowerCase();
102
157
  return MIME_BY_EXT[ext] ?? "application/octet-stream";
103
158
  }
104
159
 
105
- export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
160
+ function parseTimestamp(value: unknown): number | null {
161
+ if (typeof value !== "string") return null;
162
+ const parsed = Date.parse(value);
163
+ return Number.isFinite(parsed) ? parsed : null;
164
+ }
165
+
166
+ function upsertConversationSummaryCache(
167
+ store: ToolStore | null,
168
+ accountId: string,
169
+ conversation: ConversationListItem,
170
+ ): void {
171
+ if (!store?.upsertConversationSummary) return;
172
+ store.upsertConversationSummary({
173
+ platform: "openclaw",
174
+ accountId,
175
+ conversationId: conversation.id,
176
+ conversationType: conversation.type,
177
+ lastSeenAt: parseTimestamp(conversation.updated_at),
178
+ lastRefreshedAt: Date.now(),
179
+ raw: conversation,
180
+ });
181
+ }
182
+
183
+ function upsertConversationDetailsCache(
184
+ store: ToolStore | null,
185
+ accountId: string,
186
+ conversation: ConversationDetails,
187
+ ): void {
188
+ if (!store?.upsertConversationDetails) return;
189
+ const refreshedAt = Date.now();
190
+ store.upsertConversationDetails({
191
+ platform: "openclaw",
192
+ accountId,
193
+ conversationId: conversation.id,
194
+ conversationType: conversation.type,
195
+ lastSeenAt: parseTimestamp(conversation.updated_at),
196
+ lastRefreshedAt: refreshedAt,
197
+ raw: conversation,
198
+ members: conversation.participants.map((participant) => ({
199
+ userId: participant.user_id,
200
+ role: participant.role,
201
+ raw: participant,
202
+ lastSeenAt: parseTimestamp(participant.joined_at),
203
+ })),
204
+ membersComplete: true,
205
+ });
206
+ }
207
+
208
+ function isConversationNotFound(err: ClawlingApiError): boolean {
209
+ return err.meta?.status === 404 || err.meta?.code === 404 || err.meta?.code === 40401;
210
+ }
211
+
212
+ export function registerOpenclawClawlingTools(
213
+ api: OpenClawPluginApi,
214
+ options: { store?: ToolStore | null } = {},
215
+ ): void {
106
216
  if (!api.config) {
107
217
  api.logger.debug?.("openclaw-clawchat: api.config missing; skipping tool registration");
108
218
  return;
@@ -113,6 +223,82 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
113
223
  return resolveOpenclawClawlingAccount(api.config!);
114
224
  }
115
225
 
226
+ function resolveStore(): ToolStore | null {
227
+ if ("store" in options) return options.store ?? null;
228
+ try {
229
+ const stateDir = api.runtime.state?.resolveStateDir?.();
230
+ return getClawChatStore({
231
+ ...(stateDir ? { dbPath: clawChatDbPathForStateDir(stateDir) } : {}),
232
+ log: { error: (message) => api.logger.error?.(message) },
233
+ });
234
+ } catch {
235
+ api.logger.error?.("openclaw-clawchat sqlite tool persistence unavailable; continuing");
236
+ return null;
237
+ }
238
+ }
239
+
240
+ function detailsError(details: unknown): string | null {
241
+ if (!details || typeof details !== "object") return null;
242
+ const raw = details as { error?: unknown; message?: unknown };
243
+ if (typeof raw.error !== "string" || !raw.error) return null;
244
+ return typeof raw.message === "string" && raw.message
245
+ ? `${raw.error}: ${raw.message}`
246
+ : raw.error;
247
+ }
248
+
249
+ function persistToolCall(input: Parameters<ToolStore["recordToolCall"]>[0]): void {
250
+ try {
251
+ resolveStore()?.recordToolCall(input);
252
+ } catch {
253
+ api.logger.error?.("openclaw-clawchat sqlite tool call insert failed; continuing");
254
+ }
255
+ }
256
+
257
+ function persistConversationCache(fn: (store: ToolStore) => void): void {
258
+ try {
259
+ const store = resolveStore();
260
+ if (store) fn(store);
261
+ } catch {
262
+ api.logger.error?.("openclaw-clawchat sqlite conversation cache update failed; continuing");
263
+ }
264
+ }
265
+
266
+ async function recordClawchatToolCall<T>(
267
+ toolName: string,
268
+ params: unknown,
269
+ fn: () => Promise<OpenClawAgentToolResult<T>>,
270
+ ): Promise<OpenClawAgentToolResult<T>> {
271
+ const startedAt = Date.now();
272
+ const account = resolveCurrent();
273
+ try {
274
+ const result = await fn();
275
+ const details = (result as { details?: unknown }).details ?? result;
276
+ persistToolCall({
277
+ platform: "openclaw",
278
+ accountId: account.accountId,
279
+ toolName,
280
+ args: params ?? {},
281
+ result: details,
282
+ error: detailsError(details),
283
+ startedAt,
284
+ endedAt: Date.now(),
285
+ });
286
+ return result;
287
+ } catch (err) {
288
+ persistToolCall({
289
+ platform: "openclaw",
290
+ accountId: account.accountId,
291
+ toolName,
292
+ args: params ?? {},
293
+ result: null,
294
+ error: err instanceof Error ? err.message : String(err),
295
+ startedAt,
296
+ endedAt: Date.now(),
297
+ });
298
+ throw err;
299
+ }
300
+ }
301
+
116
302
  type ClientResult =
117
303
  | { ok: false; error: OpenClawAgentToolResult<unknown> }
118
304
  | { ok: true; client: ReturnType<typeof createOpenclawClawlingApiClient> };
@@ -151,6 +337,327 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
151
337
  })();
152
338
  }
153
339
 
340
+ function workspaceRoot(ctx: OpenClawPluginToolContext): string | null {
341
+ const root = typeof ctx.workspaceDir === "string" ? ctx.workspaceDir.trim() : "";
342
+ return root || null;
343
+ }
344
+
345
+ function targetFromParams(params: {
346
+ targetType: ClawChatMemoryTargetType;
347
+ targetId: string;
348
+ }) {
349
+ return { targetType: params.targetType, targetId: params.targetId };
350
+ }
351
+
352
+ function validateTarget(root: string, params: {
353
+ targetType: ClawChatMemoryTargetType;
354
+ targetId: string;
355
+ }): string | null {
356
+ try {
357
+ resolveClawChatMemoryPath(root, targetFromParams(params));
358
+ return null;
359
+ } catch (err) {
360
+ return err instanceof Error ? err.message : String(err);
361
+ }
362
+ }
363
+
364
+ function normalizeReadBounds(params: ClawchatMemoryReadParams): {
365
+ offset: number;
366
+ limit: number;
367
+ } {
368
+ const offset = typeof params.offset === "number" && Number.isFinite(params.offset)
369
+ ? Math.max(0, Math.trunc(params.offset))
370
+ : 0;
371
+ const limit = typeof params.limit === "number" && Number.isFinite(params.limit)
372
+ ? Math.min(MEMORY_READ_MAX_LIMIT, Math.max(1, Math.trunc(params.limit)))
373
+ : MEMORY_READ_DEFAULT_LIMIT;
374
+ return { offset, limit };
375
+ }
376
+
377
+ function metadataPatchAllowedKeys(targetType: ClawChatMemoryTargetType): readonly string[] {
378
+ if (targetType === "group") return ["title", "description"];
379
+ if (targetType === "owner") return ["nickname", "avatar_url", "bio", "behavior"];
380
+ return ["nickname", "avatar_url", "bio"];
381
+ }
382
+
383
+ function validateMetadataUpdatePatch(params: ClawchatMetadataUpdateParams): string | null {
384
+ const patch = params.patch as Record<string, unknown> | undefined;
385
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
386
+ return "openclaw-clawchat: metadata patch is required";
387
+ }
388
+ const keys = Object.keys(patch);
389
+ if (keys.length === 0) {
390
+ return "openclaw-clawchat: metadata patch must include at least one mutable field";
391
+ }
392
+ const allowed = new Set(metadataPatchAllowedKeys(params.targetType));
393
+ const invalid = keys.filter((key) => !allowed.has(key));
394
+ if (invalid.length > 0) {
395
+ return `openclaw-clawchat: metadata patch contains unsupported field(s): ${invalid.join(", ")}`;
396
+ }
397
+ const nonString = keys.filter((key) => typeof patch[key] !== "string");
398
+ if (nonString.length > 0) {
399
+ return `openclaw-clawchat: metadata patch field(s) must be strings: ${nonString.join(", ")}`;
400
+ }
401
+ return null;
402
+ }
403
+
404
+ function isUnsafeMemoryPathError(message: string): boolean {
405
+ return /Unsafe clawchat memory|Resolved clawchat memory path is outside root/i.test(message);
406
+ }
407
+
408
+ function memoryToolError(err: unknown) {
409
+ if (err instanceof ClawlingApiError) return apiError(err);
410
+ const message = err instanceof Error ? err.message : String(err);
411
+ if (isUnsafeMemoryPathError(message)) {
412
+ return validationError("openclaw-clawchat: unsafe ClawChat memory target");
413
+ }
414
+ return validationError(message);
415
+ }
416
+
417
+ function registerMemoryTools(): void {
418
+ api.registerTool(
419
+ (ctx) => ({
420
+ name: "clawchat_memory_read",
421
+ label: "Read ClawChat Memory",
422
+ description: memoryToolDescription(
423
+ "Read a ClawChat memory Markdown file by explicit targetType and targetId. " +
424
+ "Use this when you need existing owner, user, or group memory content. " +
425
+ "Do not guess ids from names. " +
426
+ "This reads metadata and agent-authored body; it does not contact the ClawChat server.",
427
+ ),
428
+ parameters: ClawchatMemoryReadSchema,
429
+ async execute(_callId, params) {
430
+ const root = workspaceRoot(ctx);
431
+ if (!root) {
432
+ return validationError("openclaw-clawchat: OpenClaw workspaceDir is required");
433
+ }
434
+ try {
435
+ const p = params as ClawchatMemoryReadParams;
436
+ const targetError = validateTarget(root, p);
437
+ if (targetError) return memoryToolError(new Error(targetError));
438
+ const { offset, limit } = normalizeReadBounds(p);
439
+ const file = await readClawChatMemoryFile(root, targetFromParams(p));
440
+ if (!file.exists) {
441
+ return jsonResponse({
442
+ exists: false,
443
+ content: "",
444
+ offset,
445
+ limit,
446
+ truncated: false,
447
+ totalLength: 0,
448
+ nextOffset: null,
449
+ });
450
+ }
451
+ const totalLength = file.raw.length;
452
+ const end = Math.min(totalLength, offset + limit);
453
+ const truncated = end < totalLength;
454
+ return jsonResponse({
455
+ exists: true,
456
+ content: file.raw.slice(offset, end),
457
+ offset,
458
+ limit,
459
+ truncated,
460
+ totalLength,
461
+ nextOffset: truncated ? end : null,
462
+ });
463
+ } catch (err) {
464
+ return memoryToolError(err);
465
+ }
466
+ },
467
+ }),
468
+ { name: "clawchat_memory_read" },
469
+ );
470
+
471
+ api.registerTool(
472
+ (ctx) => ({
473
+ name: "clawchat_memory_write",
474
+ label: "Write ClawChat Memory",
475
+ description: memoryToolDescription(
476
+ "Append to or replace only the agent-authored body of a ClawChat memory Markdown file by explicit targetType and targetId. " +
477
+ "This never modifies the metadata block. " +
478
+ "Do not use this to write or refresh ClawChat profile/metadata fields such as nickname, avatar_url, bio, profile_type, title, description, or behavior. " +
479
+ "When the user asks to refresh, sync, or update local ClawChat owner/user/group profile information, use clawchat_metadata_sync with direction=pull instead. " +
480
+ "Use append for new long-term memory notes and replace only when intentionally rewriting the whole body.",
481
+ ),
482
+ parameters: ClawchatMemoryWriteSchema,
483
+ async execute(_callId, params) {
484
+ const root = workspaceRoot(ctx);
485
+ if (!root) {
486
+ return validationError("openclaw-clawchat: OpenClaw workspaceDir is required");
487
+ }
488
+ try {
489
+ const p = params as ClawchatMemoryWriteParams;
490
+ const targetError = validateTarget(root, p);
491
+ if (targetError) return memoryToolError(new Error(targetError));
492
+ await writeClawChatMemoryBody(root, targetFromParams(p), p.mode, p.content);
493
+ return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId, mode: p.mode });
494
+ } catch (err) {
495
+ return memoryToolError(err);
496
+ }
497
+ },
498
+ }),
499
+ { name: "clawchat_memory_write" },
500
+ );
501
+
502
+ api.registerTool(
503
+ (ctx) => ({
504
+ name: "clawchat_memory_edit",
505
+ label: "Edit ClawChat Memory",
506
+ description: memoryToolDescription(
507
+ "Replace exactly one existing text span in the agent-authored body of a ClawChat memory Markdown file. " +
508
+ "This never modifies the metadata block. " +
509
+ "Do not use this to edit ClawChat profile/metadata fields such as nickname, avatar_url, bio, profile_type, title, description, or behavior. " +
510
+ "Use clawchat_metadata_sync or clawchat_metadata_update for metadata. " +
511
+ "The oldText must match exactly once; use read first when unsure.",
512
+ ),
513
+ parameters: ClawchatMemoryEditSchema,
514
+ async execute(_callId, params) {
515
+ const root = workspaceRoot(ctx);
516
+ if (!root) {
517
+ return validationError("openclaw-clawchat: OpenClaw workspaceDir is required");
518
+ }
519
+ try {
520
+ const p = params as ClawchatMemoryEditParams;
521
+ const targetError = validateTarget(root, p);
522
+ if (targetError) return memoryToolError(new Error(targetError));
523
+ await editClawChatMemoryBody(root, targetFromParams(p), p.oldText, p.newText);
524
+ return jsonResponse({ ok: true, targetType: p.targetType, targetId: p.targetId });
525
+ } catch (err) {
526
+ return memoryToolError(err);
527
+ }
528
+ },
529
+ }),
530
+ { name: "clawchat_memory_edit" },
531
+ );
532
+
533
+ api.registerTool(
534
+ (ctx) => ({
535
+ name: "clawchat_metadata_sync",
536
+ label: "Sync ClawChat Metadata",
537
+ description: memoryToolDescription(
538
+ "Synchronize the ClawChat metadata block for an explicit owner, user, or group target. " +
539
+ "Use direction=pull when the user asks to refresh, sync, or update local ClawChat profile information, user information, group information, or owner behavior from the server; this fetches the authoritative server record and rewrites only the metadata block. " +
540
+ "Use direction=push only to push selected existing local metadata fields to the server, then refresh the metadata block from the server response. " +
541
+ "For direction=push, pass non-empty fields containing only pushable metadata field names. " +
542
+ "This does not modify the agent-authored body. " +
543
+ "Do not combine clawchat_get_user_profile with clawchat_memory_write to update local profile metadata.",
544
+ ),
545
+ parameters: ClawchatMetadataSyncSchema,
546
+ async execute(_callId, params) {
547
+ const root = workspaceRoot(ctx);
548
+ if (!root) {
549
+ return validationError("openclaw-clawchat: OpenClaw workspaceDir is required");
550
+ }
551
+ const built = buildClient();
552
+ if (!built.ok) return built.error;
553
+ try {
554
+ const p = params as ClawchatMetadataSyncParams;
555
+ const targetError = validateTarget(root, p);
556
+ if (targetError) return memoryToolError(new Error(targetError));
557
+ const account = resolveCurrent();
558
+ if (p.direction === "push") {
559
+ if (!Array.isArray(p.fields) || p.fields.length === 0) {
560
+ return validationError("openclaw-clawchat: fields are required for metadata push");
561
+ }
562
+ if (p.targetType === "owner" && !account.agentId) {
563
+ return configError("openclaw-clawchat: agentId is required");
564
+ }
565
+ if (p.targetType === "user" && !account.userId) {
566
+ return configError("openclaw-clawchat: userId is required");
567
+ }
568
+ return jsonResponse(
569
+ await pushMetadata({
570
+ memoryRoot: root,
571
+ targetType: p.targetType,
572
+ targetId: p.targetId,
573
+ fields: p.fields,
574
+ agentId: account.agentId,
575
+ accountUserId: account.userId,
576
+ api: built.client,
577
+ }),
578
+ );
579
+ }
580
+ if (p.targetType === "owner") {
581
+ if (!account.agentId) return configError("openclaw-clawchat: agentId is required");
582
+ return jsonResponse(
583
+ await pullOwnerMetadata({
584
+ memoryRoot: root,
585
+ agentId: account.agentId,
586
+ accountUserId: account.userId,
587
+ accountOwnerUserId: account.ownerUserId || undefined,
588
+ api: built.client,
589
+ }),
590
+ );
591
+ }
592
+ if (p.targetType === "user") {
593
+ return jsonResponse(
594
+ await pullUserMetadata({ memoryRoot: root, userId: p.targetId, api: built.client }),
595
+ );
596
+ }
597
+ return jsonResponse(
598
+ await pullGroupMetadata({ memoryRoot: root, groupId: p.targetId, api: built.client }),
599
+ );
600
+ } catch (err) {
601
+ return memoryToolError(err);
602
+ }
603
+ },
604
+ }),
605
+ { name: "clawchat_metadata_sync" },
606
+ );
607
+
608
+ api.registerTool(
609
+ (ctx) => ({
610
+ name: "clawchat_metadata_update",
611
+ label: "Update ClawChat Metadata",
612
+ description: memoryToolDescription(
613
+ "Update ClawChat server metadata for an explicit owner, connected user account, or group target, then refresh the local metadata block from the server response. " +
614
+ "Use this only when the user wants to change server-side metadata fields. " +
615
+ "To refresh local metadata from the server without changing the server, use clawchat_metadata_sync with direction=pull. " +
616
+ "This always pushes to the server first and does not modify the agent-authored body.",
617
+ ),
618
+ parameters: ClawchatMetadataUpdateSchema,
619
+ async execute(_callId, params) {
620
+ const root = workspaceRoot(ctx);
621
+ if (!root) {
622
+ return validationError("openclaw-clawchat: OpenClaw workspaceDir is required");
623
+ }
624
+ const built = buildClient();
625
+ if (!built.ok) return built.error;
626
+ try {
627
+ const p = params as ClawchatMetadataUpdateParams;
628
+ const targetError = validateTarget(root, p);
629
+ if (targetError) return memoryToolError(new Error(targetError));
630
+ const patchError = validateMetadataUpdatePatch(p);
631
+ if (patchError) return validationError(patchError);
632
+ const account = resolveCurrent();
633
+ if (p.targetType === "owner" && !account.agentId) {
634
+ return configError("openclaw-clawchat: agentId is required");
635
+ }
636
+ if (p.targetType === "user" && !account.userId) {
637
+ return configError("openclaw-clawchat: userId is required");
638
+ }
639
+ return jsonResponse(
640
+ await updateMetadata({
641
+ memoryRoot: root,
642
+ targetType: p.targetType,
643
+ targetId: p.targetId,
644
+ agentId: account.agentId,
645
+ accountUserId: account.userId,
646
+ patch: p.patch,
647
+ api: built.client,
648
+ }),
649
+ );
650
+ } catch (err) {
651
+ return memoryToolError(err);
652
+ }
653
+ },
654
+ }),
655
+ { name: "clawchat_metadata_update" },
656
+ );
657
+ }
658
+
659
+ registerMemoryTools();
660
+
154
661
  api.registerTool(
155
662
  {
156
663
  name: "clawchat_get_account_profile",
@@ -164,8 +671,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
164
671
  "Do not frame this as a human user's personal account.",
165
672
  ),
166
673
  parameters: ClawchatGetAccountProfileSchema,
167
- async execute(_callId, _params) {
168
- return await withClient((c) => c.getMyProfile());
674
+ async execute(_callId, params) {
675
+ return await recordClawchatToolCall("clawchat_get_account_profile", params, async () =>
676
+ withClient((c) => c.getMyProfile()),
677
+ );
169
678
  },
170
679
  },
171
680
  { name: "clawchat_get_account_profile" },
@@ -179,12 +688,16 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
179
688
  "Fetch a ClawChat user's public profile by userId. " +
180
689
  "TRIGGER — invoke when the user asks to look up, view, or inspect a specific ClawChat user's public profile " +
181
690
  "and provides a concrete userId. Do not guess or infer userId from a nickname/display name. " +
691
+ "This is a read-only lookup and does not update local ClawChat Memory files. " +
692
+ "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. " +
182
693
  "Use `clawchat_get_account_profile` for the agent's own connected ClawChat account unless an explicit userId is provided.",
183
694
  ),
184
695
  parameters: ClawchatGetUserProfileSchema,
185
696
  async execute(_callId, params) {
186
- const p = params as ClawchatGetUserProfileParams;
187
- return await withClient((c) => c.getUserInfo(p.userId));
697
+ return await recordClawchatToolCall("clawchat_get_user_profile", params, async () => {
698
+ const p = params as ClawchatGetUserProfileParams;
699
+ return await withClient((c) => c.getUserInfo(p.userId));
700
+ });
188
701
  },
189
702
  },
190
703
  { name: "clawchat_get_user_profile" },
@@ -195,18 +708,14 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
195
708
  name: "clawchat_list_account_friends",
196
709
  label: "List ClawChat Account Friends",
197
710
  description: toolDescription(
198
- "List friends/contacts of the agent's connected ClawChat account (the configured ClawChat account), paginated (page=1, pageSize=20 by default). " +
711
+ "List friends/contacts of the agent's connected ClawChat account (the configured ClawChat account). " +
199
712
  "These are the agent's ClawChat-platform contacts. " +
200
- "TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, friend list, or asks to show more friends with pagination.",
713
+ "TRIGGER — invoke when the user asks for this ClawChat account's friends, contacts, or friend list.",
201
714
  ),
202
715
  parameters: ClawchatListAccountFriendsSchema,
203
716
  async execute(_callId, params) {
204
- const p = (params ?? {}) as ClawchatListAccountFriendsParams;
205
- return await withClient((c) =>
206
- c.listFriends({
207
- ...(p.page !== undefined ? { page: p.page } : { page: 1 }),
208
- ...(p.pageSize !== undefined ? { pageSize: p.pageSize } : { pageSize: 20 }),
209
- }),
717
+ return await recordClawchatToolCall("clawchat_list_account_friends", params, async () =>
718
+ withClient((c) => c.listFriends()),
210
719
  );
211
720
  },
212
721
  },
@@ -224,18 +733,95 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
224
733
  ),
225
734
  parameters: ClawchatSearchUsersSchema,
226
735
  async execute(_callId, params) {
227
- const p = (params ?? {}) as ClawchatSearchUsersParams;
228
- return await withClient((c) =>
229
- c.searchUsers({
230
- ...(typeof p.q === "string" ? { q: p.q } : {}),
231
- ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
232
- }),
233
- );
736
+ return await recordClawchatToolCall("clawchat_search_users", params, async () => {
737
+ const p = (params ?? {}) as ClawchatSearchUsersParams;
738
+ return await withClient((c) =>
739
+ c.searchUsers({
740
+ ...(typeof p.q === "string" ? { q: p.q } : {}),
741
+ ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
742
+ }),
743
+ );
744
+ });
234
745
  },
235
746
  },
236
747
  { name: "clawchat_search_users" },
237
748
  );
238
749
 
750
+ api.registerTool(
751
+ {
752
+ name: "clawchat_list_conversations",
753
+ label: "List ClawChat Conversations",
754
+ description: toolDescription(
755
+ "List ClawChat direct and group conversations visible to the configured account. " +
756
+ "TRIGGER - invoke when the user asks to list, browse, inspect, or paginate ClawChat conversations, chats, groups, or direct messages. " +
757
+ "This is read-only and does not create, update, leave, dissolve, or administer conversations.",
758
+ ),
759
+ parameters: ClawchatListConversationsSchema,
760
+ async execute(_callId, params) {
761
+ return await recordClawchatToolCall("clawchat_list_conversations", params, async () => {
762
+ const p = (params ?? {}) as ClawchatListConversationsParams;
763
+ const account = resolveCurrent();
764
+ return await withClient(async (c) => {
765
+ const data = await c.listConversations({
766
+ ...(typeof p.before === "string" ? { before: p.before } : {}),
767
+ ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
768
+ });
769
+ for (const conversation of data.conversations) {
770
+ persistConversationCache((store) =>
771
+ upsertConversationSummaryCache(store, account.accountId, conversation),
772
+ );
773
+ }
774
+ return data;
775
+ });
776
+ });
777
+ },
778
+ },
779
+ { name: "clawchat_list_conversations" },
780
+ );
781
+
782
+ api.registerTool(
783
+ {
784
+ name: "clawchat_get_conversation",
785
+ label: "Get ClawChat Conversation",
786
+ description: toolDescription(
787
+ "Fetch read-only ClawChat conversation details, including group membership when returned by the API. " +
788
+ "TRIGGER - invoke when the user asks to inspect a specific ClawChat conversation or group and provides a concrete conversationId. " +
789
+ "This is read-only and does not create, update, leave, dissolve, or administer conversations.",
790
+ ),
791
+ parameters: ClawchatGetConversationSchema,
792
+ async execute(_callId, params) {
793
+ return await recordClawchatToolCall("clawchat_get_conversation", params, async () => {
794
+ const p = params as ClawchatGetConversationParams;
795
+ const account = resolveCurrent();
796
+ const built = buildClient();
797
+ if (!built.ok) return built.error;
798
+ try {
799
+ const data = await built.client.getConversation(p.conversationId);
800
+ persistConversationCache((store) =>
801
+ upsertConversationDetailsCache(store, account.accountId, data.conversation),
802
+ );
803
+ return jsonResponse(data);
804
+ } catch (err) {
805
+ if (err instanceof ClawlingApiError) {
806
+ if (isConversationNotFound(err)) {
807
+ persistConversationCache((store) =>
808
+ store.deleteConversationCache?.({
809
+ platform: "openclaw",
810
+ accountId: account.accountId,
811
+ conversationId: p.conversationId,
812
+ }),
813
+ );
814
+ }
815
+ return apiError(err);
816
+ }
817
+ return genericError(err);
818
+ }
819
+ });
820
+ },
821
+ },
822
+ { name: "clawchat_get_conversation" },
823
+ );
824
+
239
825
  api.registerTool(
240
826
  {
241
827
  name: "clawchat_list_moments",
@@ -247,13 +833,15 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
247
833
  ),
248
834
  parameters: ClawchatListMomentsSchema,
249
835
  async execute(_callId, params) {
250
- const p = (params ?? {}) as ClawchatListMomentsParams;
251
- return await withClient((c) =>
252
- c.listMoments({
253
- ...(typeof p.before === "number" ? { before: p.before } : {}),
254
- ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
255
- }),
256
- );
836
+ return await recordClawchatToolCall("clawchat_list_moments", params, async () => {
837
+ const p = (params ?? {}) as ClawchatListMomentsParams;
838
+ return await withClient((c) =>
839
+ c.listMoments({
840
+ ...(typeof p.before === "number" ? { before: p.before } : {}),
841
+ ...(typeof p.limit === "number" ? { limit: p.limit } : {}),
842
+ }),
843
+ );
844
+ });
257
845
  },
258
846
  },
259
847
  { name: "clawchat_list_moments" },
@@ -270,18 +858,20 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
270
858
  ),
271
859
  parameters: ClawchatCreateMomentSchema,
272
860
  async execute(_callId, params) {
273
- const p = (params ?? {}) as ClawchatCreateMomentParams;
274
- const text = typeof p.text === "string" ? p.text : undefined;
275
- const images = Array.isArray(p.images) ? p.images : undefined;
276
- if (!text && (!images || images.length === 0)) {
277
- return validationError("openclaw-clawchat: at least one of text or images is required");
278
- }
279
- return await withClient((c) =>
280
- c.createMoment({
281
- ...(text !== undefined ? { text } : {}),
282
- ...(images !== undefined ? { images } : {}),
283
- }),
284
- );
861
+ return await recordClawchatToolCall("clawchat_create_moment", params, async () => {
862
+ const p = (params ?? {}) as ClawchatCreateMomentParams;
863
+ const text = typeof p.text === "string" ? p.text : undefined;
864
+ const images = Array.isArray(p.images) ? p.images : undefined;
865
+ if (!text && (!images || images.length === 0)) {
866
+ return validationError("openclaw-clawchat: at least one of text or images is required");
867
+ }
868
+ return await withClient((c) =>
869
+ c.createMoment({
870
+ ...(text !== undefined ? { text } : {}),
871
+ ...(images !== undefined ? { images } : {}),
872
+ }),
873
+ );
874
+ });
285
875
  },
286
876
  },
287
877
  { name: "clawchat_create_moment" },
@@ -298,8 +888,10 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
298
888
  ),
299
889
  parameters: ClawchatDeleteMomentSchema,
300
890
  async execute(_callId, params) {
301
- const p = params as ClawchatDeleteMomentParams;
302
- return await withClient((c) => c.deleteMoment(p.momentId));
891
+ return await recordClawchatToolCall("clawchat_delete_moment", params, async () => {
892
+ const p = params as ClawchatDeleteMomentParams;
893
+ return await withClient((c) => c.deleteMoment(p.momentId));
894
+ });
303
895
  },
304
896
  },
305
897
  { name: "clawchat_delete_moment" },
@@ -316,13 +908,15 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
316
908
  ),
317
909
  parameters: ClawchatToggleMomentReactionSchema,
318
910
  async execute(_callId, params) {
319
- const p = params as ClawchatToggleMomentReactionParams;
320
- if (!p.emoji?.trim()) {
321
- return validationError("openclaw-clawchat: emoji is required");
322
- }
323
- return await withClient((c) =>
324
- c.toggleMomentReaction({ momentId: p.momentId, emoji: p.emoji }),
325
- );
911
+ return await recordClawchatToolCall("clawchat_toggle_moment_reaction", params, async () => {
912
+ const p = params as ClawchatToggleMomentReactionParams;
913
+ if (!p.emoji?.trim()) {
914
+ return validationError("openclaw-clawchat: emoji is required");
915
+ }
916
+ return await withClient((c) =>
917
+ c.toggleMomentReaction({ momentId: p.momentId, emoji: p.emoji }),
918
+ );
919
+ });
326
920
  },
327
921
  },
328
922
  { name: "clawchat_toggle_moment_reaction" },
@@ -339,13 +933,15 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
339
933
  ),
340
934
  parameters: ClawchatCreateMomentCommentSchema,
341
935
  async execute(_callId, params) {
342
- const p = params as ClawchatCreateMomentCommentParams;
343
- if (!p.text?.trim()) {
344
- return validationError("openclaw-clawchat: text is required");
345
- }
346
- return await withClient((c) =>
347
- c.createMomentComment({ momentId: p.momentId, text: p.text }),
348
- );
936
+ return await recordClawchatToolCall("clawchat_create_moment_comment", params, async () => {
937
+ const p = params as ClawchatCreateMomentCommentParams;
938
+ if (!p.text?.trim()) {
939
+ return validationError("openclaw-clawchat: text is required");
940
+ }
941
+ return await withClient((c) =>
942
+ c.createMomentComment({ momentId: p.momentId, text: p.text }),
943
+ );
944
+ });
349
945
  },
350
946
  },
351
947
  { name: "clawchat_create_moment_comment" },
@@ -362,17 +958,19 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
362
958
  ),
363
959
  parameters: ClawchatReplyMomentCommentSchema,
364
960
  async execute(_callId, params) {
365
- const p = params as ClawchatReplyMomentCommentParams;
366
- if (!p.text?.trim()) {
367
- return validationError("openclaw-clawchat: text is required");
368
- }
369
- return await withClient((c) =>
370
- c.replyMomentComment({
371
- momentId: p.momentId,
372
- replyToCommentId: p.replyToCommentId,
373
- text: p.text,
374
- }),
375
- );
961
+ return await recordClawchatToolCall("clawchat_reply_moment_comment", params, async () => {
962
+ const p = params as ClawchatReplyMomentCommentParams;
963
+ if (!p.text?.trim()) {
964
+ return validationError("openclaw-clawchat: text is required");
965
+ }
966
+ return await withClient((c) =>
967
+ c.replyMomentComment({
968
+ momentId: p.momentId,
969
+ replyToCommentId: p.replyToCommentId,
970
+ text: p.text,
971
+ }),
972
+ );
973
+ });
376
974
  },
377
975
  },
378
976
  { name: "clawchat_reply_moment_comment" },
@@ -389,10 +987,12 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
389
987
  ),
390
988
  parameters: ClawchatDeleteMomentCommentSchema,
391
989
  async execute(_callId, params) {
392
- const p = params as ClawchatDeleteMomentCommentParams;
393
- return await withClient((c) =>
394
- c.deleteMomentComment({ momentId: p.momentId, commentId: p.commentId }),
395
- );
990
+ return await recordClawchatToolCall("clawchat_delete_moment_comment", params, async () => {
991
+ const p = params as ClawchatDeleteMomentCommentParams;
992
+ return await withClient((c) =>
993
+ c.deleteMomentComment({ momentId: p.momentId, commentId: p.commentId }),
994
+ );
995
+ });
396
996
  },
397
997
  },
398
998
  { name: "clawchat_delete_moment_comment" },
@@ -420,17 +1020,19 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
420
1020
  ),
421
1021
  parameters: ClawchatUpdateAccountProfileSchema,
422
1022
  async execute(_callId, params) {
423
- const p = (params ?? {}) as ClawchatUpdateAccountProfileParams;
424
- const patch: { nickname?: string; avatar_url?: string; bio?: string } = {};
425
- if (typeof p.nickname === "string") patch.nickname = p.nickname;
426
- if (typeof p.avatar_url === "string") patch.avatar_url = p.avatar_url;
427
- if (typeof p.bio === "string") patch.bio = p.bio;
428
- if (Object.keys(patch).length === 0) {
429
- return validationError(
430
- "openclaw-clawchat: at least one of nickname / avatar / bio is required",
431
- );
432
- }
433
- return await withClient((c): Promise<Profile> => c.updateMyProfile(patch));
1023
+ return await recordClawchatToolCall("clawchat_update_account_profile", params, async () => {
1024
+ const p = (params ?? {}) as ClawchatUpdateAccountProfileParams;
1025
+ const patch: { nickname?: string; avatar_url?: string; bio?: string } = {};
1026
+ if (typeof p.nickname === "string") patch.nickname = p.nickname;
1027
+ if (typeof p.avatar_url === "string") patch.avatar_url = p.avatar_url;
1028
+ if (typeof p.bio === "string") patch.bio = p.bio;
1029
+ if (Object.keys(patch).length === 0) {
1030
+ return validationError(
1031
+ "openclaw-clawchat: at least one of nickname / avatar / bio is required",
1032
+ );
1033
+ }
1034
+ return await withClient((c): Promise<Profile> => c.updateMyProfile(patch));
1035
+ });
434
1036
  },
435
1037
  },
436
1038
  { name: "clawchat_update_account_profile" },
@@ -447,30 +1049,32 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
447
1049
  ),
448
1050
  parameters: ClawchatUploadAvatarImageSchema,
449
1051
  async execute(_callId, params) {
450
- const p = params as ClawchatUploadAvatarImageParams;
451
- if (!p.filePath || !path.isAbsolute(p.filePath)) {
452
- return validationError("openclaw-clawchat: filePath must be an absolute local path");
453
- }
454
- let stat: fs.Stats;
455
- try {
456
- stat = fs.statSync(p.filePath);
457
- } catch (err) {
458
- return validationError(
459
- `openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`,
460
- );
461
- }
462
- if (!stat.isFile()) {
463
- return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
464
- }
465
- if (stat.size > MAX_UPLOAD_BYTES) {
466
- return validationError(
467
- `openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`,
468
- );
469
- }
470
- const buffer = fs.readFileSync(p.filePath);
471
- const filename = path.basename(p.filePath);
472
- const mime = inferMimeFromPath(p.filePath);
473
- return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
1052
+ return await recordClawchatToolCall("clawchat_upload_avatar_image", params, async () => {
1053
+ const p = params as ClawchatUploadAvatarImageParams;
1054
+ if (!p.filePath || !path.isAbsolute(p.filePath)) {
1055
+ return validationError("openclaw-clawchat: filePath must be an absolute local path");
1056
+ }
1057
+ let stat: fs.Stats;
1058
+ try {
1059
+ stat = fs.statSync(p.filePath);
1060
+ } catch (err) {
1061
+ return validationError(
1062
+ `openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`,
1063
+ );
1064
+ }
1065
+ if (!stat.isFile()) {
1066
+ return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
1067
+ }
1068
+ if (stat.size > MAX_UPLOAD_BYTES) {
1069
+ return validationError(
1070
+ `openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`,
1071
+ );
1072
+ }
1073
+ const buffer = fs.readFileSync(p.filePath);
1074
+ const filename = path.basename(p.filePath);
1075
+ const mime = inferMimeFromPath(p.filePath);
1076
+ return await withClient((c) => c.uploadAvatar({ buffer, filename, mime }));
1077
+ });
474
1078
  },
475
1079
  },
476
1080
  { name: "clawchat_upload_avatar_image" },
@@ -488,36 +1092,38 @@ export function registerOpenclawClawlingTools(api: OpenClawPluginApi): void {
488
1092
  ),
489
1093
  parameters: ClawchatUploadMediaFileSchema,
490
1094
  async execute(_callId, params) {
491
- const p = params as ClawchatUploadMediaFileParams;
492
- if (!p.filePath || !path.isAbsolute(p.filePath)) {
493
- return validationError("openclaw-clawchat: filePath must be an absolute local path");
494
- }
495
- let stat: fs.Stats;
496
- try {
497
- stat = fs.statSync(p.filePath);
498
- } catch (err) {
499
- return validationError(
500
- `openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`,
501
- );
502
- }
503
- if (!stat.isFile()) {
504
- return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
505
- }
506
- if (stat.size > MAX_UPLOAD_BYTES) {
507
- return validationError(
508
- `openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`,
509
- );
510
- }
511
- const buffer = fs.readFileSync(p.filePath);
512
- const filename = path.basename(p.filePath);
513
- const mime = inferMimeFromPath(p.filePath);
514
- return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
1095
+ return await recordClawchatToolCall("clawchat_upload_media_file", params, async () => {
1096
+ const p = params as ClawchatUploadMediaFileParams;
1097
+ if (!p.filePath || !path.isAbsolute(p.filePath)) {
1098
+ return validationError("openclaw-clawchat: filePath must be an absolute local path");
1099
+ }
1100
+ let stat: fs.Stats;
1101
+ try {
1102
+ stat = fs.statSync(p.filePath);
1103
+ } catch (err) {
1104
+ return validationError(
1105
+ `openclaw-clawchat: cannot stat ${p.filePath}: ${err instanceof Error ? err.message : String(err)}`,
1106
+ );
1107
+ }
1108
+ if (!stat.isFile()) {
1109
+ return validationError(`openclaw-clawchat: ${p.filePath} is not a regular file`);
1110
+ }
1111
+ if (stat.size > MAX_UPLOAD_BYTES) {
1112
+ return validationError(
1113
+ `openclaw-clawchat: file too large (${stat.size} bytes; max 20MB)`,
1114
+ );
1115
+ }
1116
+ const buffer = fs.readFileSync(p.filePath);
1117
+ const filename = path.basename(p.filePath);
1118
+ const mime = inferMimeFromPath(p.filePath);
1119
+ return await withClient((c) => c.uploadMedia({ buffer, filename, mime }));
1120
+ });
515
1121
  },
516
1122
  },
517
1123
  { name: "clawchat_upload_media_file" },
518
1124
  );
519
1125
 
520
1126
  api.logger.debug?.(
521
- "openclaw-clawchat: registered 14 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, search_users, 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)",
1127
+ "openclaw-clawchat: registered 21 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, memory_read, memory_write, memory_edit, metadata_sync, metadata_update)",
522
1128
  );
523
1129
  }