@clawling/clawchat-plugin-openclaw 2026.10.7-1 → 2026.10.7-2

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.
@@ -859,6 +859,10 @@ export async function startOpenclawClawlingGateway(params) {
859
859
  // `sendOwnerSkillMessage` below) — both live in this same function closure, so
860
860
  // the forward reference here just resolves to the fresh value at call time.
861
861
  let livewareSampleNotify = async () => false;
862
+ // The supervisor this gateway call shares (new or adopted). The abort cleanup
863
+ // below awaits its stop() so the host's channel stop (which waits on this
864
+ // task) does not return while liveware children are still being killed.
865
+ let livewareSampleForShutdown = null;
862
866
  if (store) {
863
867
  const stateDirForSample = runtime.state?.resolveStateDir?.();
864
868
  const livewareSampleDeps = {
@@ -901,6 +905,7 @@ export async function startOpenclawClawlingGateway(params) {
901
905
  livewareSampleSupervisors.set(abortSignal, supervisorsForSignal);
902
906
  const supervisorKey = `openclaw:${accountId}`;
903
907
  const runningSupervisor = supervisorsForSignal.get(supervisorKey);
908
+ livewareSampleForShutdown = runningSupervisor ?? null;
904
909
  if (runningSupervisor) {
905
910
  // A gateway re-entry (refresh/backoff reconnect), not a fresh channel start:
906
911
  // hand the live supervisor this closure's token + REST client + notify path
@@ -910,9 +915,12 @@ export async function startOpenclawClawlingGateway(params) {
910
915
  }
911
916
  else {
912
917
  const livewareSampleSupervisor = new LivewareSampleSupervisor(livewareSampleDeps);
918
+ livewareSampleForShutdown = livewareSampleSupervisor;
913
919
  supervisorsForSignal.set(supervisorKey, livewareSampleSupervisor);
914
920
  abortSignal.addEventListener("abort", () => {
915
- livewareSampleSupervisor.stop();
921
+ // Starts the SIGTERM → SIGKILL teardown at once; the gateway's own
922
+ // abort cleanup awaits the same (idempotent) stop() promise.
923
+ void livewareSampleSupervisor.stop();
916
924
  if (supervisorsForSignal.get(supervisorKey) === livewareSampleSupervisor) {
917
925
  supervisorsForSignal.delete(supervisorKey);
918
926
  }
@@ -1188,6 +1196,25 @@ export async function startOpenclawClawlingGateway(params) {
1188
1196
  const persistOwnerProfile = upsertOwnerProfile
1189
1197
  ? makeOwnerProfilePersister({ upsertOwnerProfile }, { accountId })
1190
1198
  : undefined;
1199
+ // The owner_profile row is where owner-language copy reads its locale
1200
+ // (framework errors, greetings, liveware intros). It was only refreshed on
1201
+ // owner metadata pulls, so an owner who switched app language kept getting
1202
+ // the old one. friend.profile_updated also reaches the agent when the
1203
+ // owner edits their profile; refetch just the owner row then.
1204
+ const refreshOwnerProfileOnUpdate = (entityId) => {
1205
+ const subject = typeof entityId === "string" ? entityId.trim() : "";
1206
+ if (!persistOwnerProfile || !subject || subject !== account.ownerUserId)
1207
+ return;
1208
+ void (async () => {
1209
+ try {
1210
+ const owner = await getConversationApiClient().getAgentOwner();
1211
+ persistOwnerProfile(owner);
1212
+ }
1213
+ catch (err) {
1214
+ log?.error?.(`[${accountId}] clawchat-plugin-openclaw owner profile refresh failed source=friend.profile_updated: ${err instanceof Error ? err.message : String(err)}`);
1215
+ }
1216
+ })();
1217
+ };
1191
1218
  const refreshAgentBehavior = async (options) => {
1192
1219
  try {
1193
1220
  await pullOwnerMetadata({
@@ -2305,6 +2332,9 @@ export async function startOpenclawClawlingGateway(params) {
2305
2332
  if (type === "friend.added") {
2306
2333
  scheduleFriendGreeting(payload);
2307
2334
  }
2335
+ if (type === "friend.profile_updated") {
2336
+ refreshOwnerProfileOnUpdate(payload?.entity_id);
2337
+ }
2308
2338
  }
2309
2339
  }
2310
2340
  });
@@ -2409,6 +2439,8 @@ export async function startOpenclawClawlingGateway(params) {
2409
2439
  ? groupFile.metadata.group_owner_nickname ?? metadata?.nickname ?? userId
2410
2440
  : metadata?.nickname ?? userId;
2411
2441
  const profileType = metadata?.profile_type ?? (userId === account.userId ? "agent" : "user");
2442
+ if (name !== userId)
2443
+ knownNicknames.set(userId, name);
2412
2444
  participants.push({ id: userId, name, profileType, isAgentOwner, isGroupOwner });
2413
2445
  }
2414
2446
  return participants;
@@ -2444,6 +2476,7 @@ export async function startOpenclawClawlingGateway(params) {
2444
2476
  basePrompt: turn.peer.kind === "group" ? getClawChatGroupPrompt() : getClawChatUserPrompt(),
2445
2477
  ...promptMetadata,
2446
2478
  ownerIdFallback: account.ownerUserId,
2479
+ currentAgentIdFallback: account.userId,
2447
2480
  groupParticipants,
2448
2481
  turn: {
2449
2482
  chatType: promptChatType,
@@ -2463,9 +2496,76 @@ export async function startOpenclawClawlingGateway(params) {
2463
2496
  },
2464
2497
  });
2465
2498
  };
2499
+ // Group frames often carry the sender's id as nick_name. Prefer the nickname
2500
+ // already cached in ClawChat memory (owner.md for the owner and this agent,
2501
+ // users/<id>.md for everyone else, the group's group_owner_nickname for its
2502
+ // owner); keep the id when nothing is cached.
2503
+ const cachedNicknameFor = async (memoryRoot, userId) => {
2504
+ try {
2505
+ if (userId === account.ownerUserId || userId === account.userId) {
2506
+ const ownerFile = await readClawChatMemoryFile(memoryRoot, { targetType: "owner", targetId: "owner" });
2507
+ const nickname = userId === account.userId
2508
+ ? ownerFile.metadata.agent_nickname
2509
+ : ownerFile.metadata.agent_owner_nickname;
2510
+ if (nickname)
2511
+ return nickname;
2512
+ }
2513
+ const userFile = await readClawChatMemoryFile(memoryRoot, { targetType: "user", targetId: userId });
2514
+ return userFile.exists && userFile.metadata.nickname ? userFile.metadata.nickname : null;
2515
+ }
2516
+ catch {
2517
+ return null;
2518
+ }
2519
+ };
2520
+ // Nicknames seen while building group prompts, so ingest can label a
2521
+ // coalesced batch's transcript ("Alice:" rather than "usr_…:") without
2522
+ // touching disk.
2523
+ const knownNicknames = new Map();
2524
+ const withCachedGroupSenderNames = async (turn, memoryRoot) => {
2525
+ if (turn.peer.kind !== "group" || !memoryRoot)
2526
+ return turn;
2527
+ const isBareId = (name, id) => !name || name === id;
2528
+ const cache = new Map();
2529
+ let groupOwner = null;
2530
+ const lookup = async (userId) => {
2531
+ if (!cache.has(userId)) {
2532
+ let nickname = await cachedNicknameFor(memoryRoot, userId);
2533
+ if (!nickname) {
2534
+ if (!groupOwner) {
2535
+ try {
2536
+ const groupFile = await readClawChatMemoryFile(memoryRoot, { targetType: "group", targetId: turn.peer.id });
2537
+ groupOwner = { id: groupFile.metadata.group_owner_id, nickname: groupFile.metadata.group_owner_nickname };
2538
+ }
2539
+ catch {
2540
+ groupOwner = {};
2541
+ }
2542
+ }
2543
+ if (groupOwner.id === userId && groupOwner.nickname)
2544
+ nickname = groupOwner.nickname;
2545
+ }
2546
+ cache.set(userId, nickname);
2547
+ if (nickname)
2548
+ knownNicknames.set(userId, nickname);
2549
+ }
2550
+ return cache.get(userId) ?? null;
2551
+ };
2552
+ const senderNickName = isBareId(turn.senderNickName, turn.senderId)
2553
+ ? (await lookup(turn.senderId)) ?? turn.senderNickName
2554
+ : turn.senderNickName;
2555
+ const groupMessages = turn.groupMessages
2556
+ ? await Promise.all(turn.groupMessages.map(async (message) => isBareId(message.senderName, message.senderId)
2557
+ ? { ...message, senderName: (await lookup(message.senderId)) ?? message.senderName }
2558
+ : message))
2559
+ : turn.groupMessages;
2560
+ return { ...turn, senderNickName, ...(groupMessages ? { groupMessages } : {}) };
2561
+ };
2466
2562
  const resolveSenderNickNameForTurn = (turn) => {
2467
2563
  if (turn.senderNickName && turn.senderNickName !== turn.senderId)
2468
2564
  return turn.senderNickName;
2565
+ // Synchronous on purpose: an await here would reorder same-group frames.
2566
+ const known = turn.peer.kind === "group" ? knownNicknames.get(turn.senderId) : undefined;
2567
+ if (known)
2568
+ return known;
2469
2569
  return turn.senderNickName || turn.senderId;
2470
2570
  };
2471
2571
  const resolveSenderBatchIdentityForTurn = (turn) => {
@@ -2480,7 +2580,9 @@ export async function startOpenclawClawlingGateway(params) {
2480
2580
  senderProfileType: turn.senderProfileType ??
2481
2581
  (senderRelation === "self_agent" || senderRelation === "peer_agent" ? "agent" : "user"),
2482
2582
  senderIsOwner: senderRelation === "owner",
2483
- senderIsGroupOwner: turn.senderIsGroupOwner ?? false,
2583
+ // Left undefined when the frame does not say, so the prompt falls back
2584
+ // to the group metadata's group_owner_id instead of a forced false.
2585
+ ...(turn.senderIsGroupOwner !== undefined ? { senderIsGroupOwner: turn.senderIsGroupOwner } : {}),
2484
2586
  };
2485
2587
  };
2486
2588
  const claimInboundTurn = (turn) => {
@@ -2550,6 +2652,7 @@ export async function startOpenclawClawlingGateway(params) {
2550
2652
  catch (err) {
2551
2653
  log?.error?.(`[${accountId}] clawchat-plugin-openclaw message metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`);
2552
2654
  }
2655
+ turn = await withCachedGroupSenderNames(turn, memoryRoot);
2553
2656
  const conversationTarget = `${CHANNEL_ID}:${formatConversationSubject(turn.peer)}`;
2554
2657
  const turnPrompt = await buildPromptForTurn(turn, memoryRoot);
2555
2658
  const senderIsOwner = turn.senderIsOwner === true || turn.senderId === account.ownerUserId;
@@ -2680,6 +2783,7 @@ export async function startOpenclawClawlingGateway(params) {
2680
2783
  runtime,
2681
2784
  account,
2682
2785
  client,
2786
+ resolveOwnerLocale: () => store?.getOwnerProfile?.({ platform: "openclaw", accountId })?.locale ?? null,
2683
2787
  target: { chatId: turn.peer.id, chatType: turn.peer.kind },
2684
2788
  ...(replyCtx ? { replyCtx } : {}),
2685
2789
  inboundMessageId: turn.messageId,
@@ -3270,6 +3374,16 @@ export async function startOpenclawClawlingGateway(params) {
3270
3374
  closeReason: "client close",
3271
3375
  });
3272
3376
  client.close();
3377
+ if (livewareSampleForShutdown) {
3378
+ // SIGTERM, then SIGKILL after KILL_GRACE_MS (3 s): inside the host's
3379
+ // 5 s wait for this task, so a stuck child no longer outlives the stop.
3380
+ try {
3381
+ await livewareSampleForShutdown.stop();
3382
+ }
3383
+ catch (err) {
3384
+ log?.error?.(`[${accountId}] clawchat-plugin-openclaw liveware-sample stop failed: ${err instanceof Error ? err.message : String(err)}`);
3385
+ }
3386
+ }
3273
3387
  setStatus({
3274
3388
  ...getStatus(),
3275
3389
  connected: false,
@@ -66,7 +66,7 @@ export const OFFICIAL_SKILLS_BASE = "https://raw.githubusercontent.com/clawling/
66
66
  * in the install-cli repo, bump this constant, ship it. `liveware-sample.ts`
67
67
  * imports the same ref, so the `livewares` tree at that tag is pinned too.
68
68
  */
69
- export const DEFAULT_SKILLS_REF = "skills-v1.16.0";
69
+ export const DEFAULT_SKILLS_REF = "skills-v1.17.0";
70
70
  /** Refuse to treat an absurdly large response as a skill file (defence in depth). */
71
71
  export const MAX_SKILL_BYTES = 256 * 1024;
72
72
  /** This adapter's host target inside `skills/manifest.json`. */
@@ -205,7 +205,7 @@ export const ClawchatCreateMomentSchema = Type.Object({
205
205
  description: "Moment text. At least one of text or images is required.",
206
206
  })),
207
207
  images: Type.Optional(Type.Array(Type.String({
208
- description: "Image URL for the moment. Upload local files first; do not pass local paths.",
208
+ description: "An http(s) image URL, or an absolute local image path (the tool uploads it and uses the hosted URL).",
209
209
  }))),
210
210
  });
211
211
  export const ClawchatDeleteMomentSchema = Type.Object({
package/dist/src/tools.js CHANGED
@@ -28,6 +28,15 @@ function extractRequestId(err) {
28
28
  }
29
29
  return undefined;
30
30
  }
31
+ /**
32
+ * What the agent must do with the later `permission_result` receipt
33
+ * (agent-protocol §2.8): on approval the SERVER executes the gated call, so a
34
+ * second call would duplicate it or raise a fresh card. Only `approved_retry`
35
+ * (reads, which are never replayed) asks for exactly one more call.
36
+ */
37
+ const PERMISSION_RECEIPT_RULE = "When the result arrives: outcome \"approved\" (or \"auto_allowed\") means the server has already done it for you — do not call this tool again; " +
38
+ "only outcome \"approved_retry\" means call this same tool once more with the same arguments. " +
39
+ "Anything the server produced (a new id, a code, read data) comes in that message's Result line.";
31
40
  function permissionGateResult(err) {
32
41
  const code = err.meta?.code;
33
42
  const requestId = extractRequestId(err);
@@ -38,7 +47,8 @@ function permissionGateResult(err) {
38
47
  "This operation requires the owner's approval and has been submitted for review" +
39
48
  (requestId ? ` (request_id=${requestId})` : "") +
40
49
  ". It has NOT failed. Do not retry — the result will arrive later as a normal " +
41
- "chat message; wait for it instead of calling this tool again.";
50
+ "chat message; wait for it instead of calling this tool again. " +
51
+ PERMISSION_RECEIPT_RULE;
42
52
  status = "pending";
43
53
  }
44
54
  else {
@@ -120,6 +130,46 @@ function inferMimeFromPath(filePath) {
120
130
  const ext = path.extname(filePath).toLowerCase();
121
131
  return MIME_BY_EXT[ext] ?? "application/octet-stream";
122
132
  }
133
+ /**
134
+ * Classify `clawchat_create_moment` image entries: http(s) URLs pass through;
135
+ * an existing absolute local regular file (≤ 20MB) is marked for upload; any
136
+ * other value is a validation error naming the offending index.
137
+ */
138
+ function planMomentImages(images) {
139
+ const entries = [];
140
+ for (const [i, raw] of images.entries()) {
141
+ const where = `clawchat-plugin-openclaw: images[${i}]`;
142
+ const value = typeof raw === "string" ? raw.trim() : "";
143
+ if (!value) {
144
+ return { ok: false, message: `${where} must be an http(s) image URL or an absolute local image path` };
145
+ }
146
+ if (/^https?:\/\/\S+$/i.test(value)) {
147
+ entries.push({ kind: "url", url: value });
148
+ continue;
149
+ }
150
+ if (!path.isAbsolute(value)) {
151
+ return {
152
+ ok: false,
153
+ message: `${where} (${JSON.stringify(value)}) is neither an http(s) URL nor an absolute local file path; no moment was created`,
154
+ };
155
+ }
156
+ let stat;
157
+ try {
158
+ stat = fs.statSync(value);
159
+ }
160
+ catch {
161
+ return { ok: false, message: `${where}: local file ${value} does not exist; no moment was created` };
162
+ }
163
+ if (!stat.isFile()) {
164
+ return { ok: false, message: `${where}: ${value} is not a regular file; no moment was created` };
165
+ }
166
+ if (stat.size > MAX_UPLOAD_BYTES) {
167
+ return { ok: false, message: `${where}: ${value} is too large (${stat.size} bytes; max 20MB)` };
168
+ }
169
+ entries.push({ kind: "local", path: value });
170
+ }
171
+ return { ok: true, entries };
172
+ }
123
173
  function validateMentionMessageParams(params) {
124
174
  if (!params || typeof params !== "object") {
125
175
  return { ok: false, message: "clawchat-plugin-openclaw: params are required" };
@@ -285,6 +335,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
285
335
  ok: true,
286
336
  client: createOpenclawClawlingApiClient({
287
337
  baseUrl: acct.baseUrl,
338
+ mediaBaseUrl: acct.mediaBaseUrl,
288
339
  token: acct.token,
289
340
  userId: acct.userId,
290
341
  }),
@@ -329,8 +380,16 @@ export function registerOpenclawClawlingTools(api, options = {}) {
329
380
  ? rawData
330
381
  : {};
331
382
  const outcome = mapGateOutcome(code, gateData);
332
- if (outcome !== null)
333
- return jsonResponse(outcome);
383
+ if (outcome !== null) {
384
+ // `next_step` is added here, not in mapGateOutcome: that mapping
385
+ // is pinned byte-for-byte against the Hermes parity fixtures.
386
+ return jsonResponse(outcome.status === "pending_owner_approval"
387
+ ? {
388
+ ...outcome,
389
+ next_step: "Waiting for the owner's approval; do not call this tool again now. " + PERMISSION_RECEIPT_RULE,
390
+ }
391
+ : outcome);
392
+ }
334
393
  }
335
394
  return apiError(err);
336
395
  }
@@ -1100,7 +1159,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
1100
1159
  label: "Create ClawChat Moment",
1101
1160
  description: toolDescription("Create a new ClawChat moment/dynamic for the configured ClawChat account. " +
1102
1161
  "TRIGGER - invoke when the user asks to publish, post, or send a ClawChat moment/dynamic, such as \"post a ClawChat moment saying ...\", \"publish a dynamic ...\", or \"send a friend moment ...\". " +
1103
- "At least one of text or images must be present. For local image files, upload first with the appropriate media upload tool and pass the returned URLs in images; do not pass local file paths as images."),
1162
+ "At least one of text or images must be present. Each images entry is either an http(s) image URL (used as-is) or an absolute local image path (max 20MB) — for a local path this tool uploads it to ClawChat media itself and uses the hosted URL, so no separate upload step is needed. Any other value (relative path, file:// or data: URI, missing file) is rejected and no moment is created."),
1104
1163
  parameters: ClawchatCreateMomentSchema,
1105
1164
  async execute(_callId, params) {
1106
1165
  return await recordClawchatToolCall(accountId, "clawchat_create_moment", params, async () => {
@@ -1110,10 +1169,33 @@ export function registerOpenclawClawlingTools(api, options = {}) {
1110
1169
  if (!text && (!images || images.length === 0)) {
1111
1170
  return validationError("clawchat-plugin-openclaw: at least one of text or images is required");
1112
1171
  }
1113
- return await withGatedClient(accountId, (c) => c.createMoment({
1114
- ...(text !== undefined ? { text } : {}),
1115
- ...(images !== undefined ? { images } : {}),
1116
- }));
1172
+ // Validate every entry before touching the network, so a bad value
1173
+ // never leaves a half-uploaded batch or a moment with a broken image.
1174
+ const plan = images !== undefined ? planMomentImages(images) : { ok: true, entries: [] };
1175
+ if (!plan.ok)
1176
+ return validationError(plan.message);
1177
+ return await withGatedClient(accountId, async (c) => {
1178
+ const resolved = [];
1179
+ for (const entry of plan.entries) {
1180
+ if (entry.kind === "url") {
1181
+ resolved.push(entry.url);
1182
+ continue;
1183
+ }
1184
+ const uploaded = await c.uploadMedia({
1185
+ buffer: fs.readFileSync(entry.path),
1186
+ filename: path.basename(entry.path),
1187
+ mime: inferMimeFromPath(entry.path),
1188
+ });
1189
+ if (uploaded.kind !== "image") {
1190
+ throw new Error(`clawchat-plugin-openclaw: ${entry.path} is not an image (media service classified it as ${uploaded.kind}); no moment was created`);
1191
+ }
1192
+ resolved.push(uploaded.url);
1193
+ }
1194
+ return await c.createMoment({
1195
+ ...(text !== undefined ? { text } : {}),
1196
+ ...(images !== undefined ? { images: resolved } : {}),
1197
+ });
1198
+ });
1117
1199
  });
1118
1200
  },
1119
1201
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.10.7-1",
3
+ "version": "2026.10.7-2",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "author": "CLAWLING PTE. LTD.",
@@ -42,7 +42,8 @@
42
42
  "build": "tsc -p tsconfig.build.json",
43
43
  "test": "vitest",
44
44
  "dev:openclaw-source": "test -d tmp/openclaw || git clone --depth=1 https://github.com/openclaw/openclaw.git tmp/openclaw",
45
- "prepack": "npm run build",
45
+ "prepack": "npm run build && node tools/pack-manifest.mjs strip",
46
+ "postpack": "node tools/pack-manifest.mjs restore",
46
47
  "typecheck": "tsc --noEmit",
47
48
  "prepublishOnly": "npm run typecheck",
48
49
  "release": "npm run prepublishOnly && npm publish --tag latest"
@@ -50,12 +51,6 @@
50
51
  "dependencies": {
51
52
  "@sinclair/typebox": "0.34.48"
52
53
  },
53
- "devDependencies": {
54
- "@types/node": "^25.5.0",
55
- "openclaw": "2026.6.11",
56
- "typescript": "^5.4.0",
57
- "vitest": "^4.1.5"
58
- },
59
54
  "peerDependencies": {
60
55
  "openclaw": ">=2026.5.4"
61
56
  },
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-core
3
- version: 1.9.0
3
+ version: 1.10.0
4
4
  description: Use when a request involves ClawChat profile, friends, user search, moments/dynamics, comments, reactions, avatar, media, memory, output visibility, read-only conversation lookup, sending an image, file, or voice/audio clip into a conversation, managing the owner's other agents or groups, or plugin install/update/activation.
5
5
  ---
6
6
 
@@ -18,7 +18,7 @@ This skill guides agent behavior for ClawChat-aware tasks. Use the registered Cl
18
18
 
19
19
  ## Sending an Image, File, or Voice Message
20
20
 
21
- To deliver an image, file, or voice/audio clip into the current ClawChat conversation, use the OpenClaw **message tool** with `action='send'` and `media` set to a local file path or an HTTPS URL. This is a host message-tool capability, not a `clawchat_*` tool — the `clawchat_upload_avatar_image` / moment-image tools are a separate avatar/moments surface and do not post into a conversation.
21
+ To deliver an image, file, or voice/audio clip into the current ClawChat conversation, use the OpenClaw **message tool** with `action='send'` and `media` set to a local file path or an HTTPS URL. This is a host message-tool capability, not a `clawchat_*` tool — `clawchat_upload_avatar_image` (avatars) and the `images` of `clawchat_create_moment` (moments) are separate surfaces and do not post into a conversation.
22
22
 
23
23
  - ClawChat detects the media type from the file and renders it: images inline, audio files (`.mp3`, `.m4a`, `.wav`, `.ogg`, `.aac`, …) as **playable voice messages**, everything else as a downloadable file. There is no separate voice tool or `voice` kind — a voice message is just audio media, so sending a genuine audio file is how you "send a voice message".
24
24
  - Use a real audio file with its normal extension so its type is recognized as audio; an extension-less or mislabeled file may arrive as a plain file. The clip length is shown on the recipient side automatically — you do not set a duration.
@@ -122,7 +122,7 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
122
122
  | Add a person to a group | `clawchat_add_group_member` with the exact group `conversationId` and the person's exact `userId` (from group metadata, `clawchat_list_account_friends`, or `clawchat_search_users`; never guessed from a name), only on an explicit request. Groups only; the person must already be your friend. The owner's `group.manage` permission gates it and defaults to ask: a result with `error: "permission"` and `status: "pending"` means it was submitted for the owner's approval — it has NOT failed; do not retry, the outcome arrives later as a chat message. `status: "forbidden"` means the owner's policy blocks it; do not retry |
123
123
  | View/browse moments or dynamics | `clawchat_list_moments` |
124
124
  | Read one moment and its visible comments by exact id | `clawchat_get_moment` with exact `momentId`; read-only, use after a `moment.comment.created`/`moment.comment.replied` awareness note to read the new comment before deciding whether to reply |
125
- | Create a moment/dynamic | `clawchat_create_moment`; upload local images first and pass URLs |
125
+ | Create a moment/dynamic | `clawchat_create_moment`; each `images` entry is an http(s) URL or an absolute local file path (the plugin uploads a local file for you); anything else is rejected |
126
126
  | Delete a moment/dynamic | `clawchat_delete_moment` with an exact `momentId` |
127
127
  | React/unreact to a moment | `clawchat_toggle_moment_reaction` with exact `momentId` and emoji |
128
128
  | Top-level moment comment | `clawchat_create_moment_comment` |
@@ -130,6 +130,15 @@ Tool descriptions are authoritative. These routing hints resolve common ambiguit
130
130
  | Delete a comment/reply | `clawchat_delete_moment_comment` with exact `momentId` and `commentId` |
131
131
  | Nickname or bio update | `clawchat_update_account_profile` |
132
132
 
133
+ ## Owner Approvals (Permission Receipts)
134
+
135
+ Some operations are gated by the owner's permissions. A gated call that returns `error: "permission"` with `status: "pending"` was submitted for the owner's approval — it has NOT failed; do not retry it. The outcome arrives later as a permission receipt, a system message in your chat with the owner:
136
+
137
+ - **`approved`** — the server already carried out the operation for you. It is done: do not call it again. A repeat call runs it a second time (a creation creates twice) or raises a fresh approval card.
138
+ - **`approved_retry`** — the only outcome that asks you to call again: make the same call once more.
139
+ - **Approved reads** return their data in the receipt's `result` (for example a group invite `code`). Use what is there instead of calling the read again.
140
+ - `denied`, `expired`, or `failed` — tell the user what happened; do not retry on your own.
141
+
133
142
  ## Managing The Owner's Other Agents
134
143
 
135
144
  When the owner asks you to manage their **other** agents or their **groups** —
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-orchestration
3
- version: 2.1.0
3
+ version: 2.2.0
4
4
  description: Use when the owner asks this agent to manage their OTHER ClawChat agents or their groups — 编排 / orchestrate a fleet, read or rewrite another agent's 提示词 / system prompt / behavior, 禁言 / mute an agent, change 回复模式 / reply mode, stop 刷屏 / flooding in a group, 建群 / create a group of agents, add or remove agents from a group, or 签发连接码 / issue a connect code.
5
5
  ---
6
6
 
@@ -169,6 +169,7 @@ throw is **not** by itself success — read the `code` field.
169
169
  | Code | Means | Do |
170
170
  | --- | --- | --- |
171
171
  | `21003` | Owner has not turned on 云端编排 / Cloud orchestration — **the common case** | Ask the owner to turn it on in your permission settings. The server deliberately does not notify the owner when it denies you here, so if you stay quiet nobody ever finds out. Do not retry |
172
+ | `21001` | The operation is waiting for the owner's approval; nothing has happened yet | Tell the owner it is waiting on them, then stop — do not call again now. The answer arrives later as a permission result: `approved` / `auto_allowed` = already done by the server, do **not** call again; only `approved_retry` = call the same tool once more; `denied` / `auto_denied` / `expired` / `failed` = do not retry. A `result` on it holds what the operation produced (e.g. `conversation_id`, connect `code`) — use that instead of calling again |
172
173
  | `403` | Insufficient scope — the `agent:orchestrate` scope is missing (rare; it is a default scope) | Report. Do not retry |
173
174
  | `401` | Credentials stale or revoked | Report once. Do not loop |
174
175
  | `16025` | Connect-code rate limit; the bucket is your owner's, shared with their own manual issuance | Wait. Do not hammer it |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clawchat-set-greeting
3
- version: 1.2.0
3
+ version: 1.2.1
4
4
  description: Use when the user wants to customize, change, set, or reset this agent's greetings — the first-load / activation greeting to the owner (~/clawchat/greeting.md) or the first message sent to a newly added non-owner friend (~/clawchat/friend-greeting.md).
5
5
  ---
6
6
 
@@ -58,8 +58,8 @@ Override it the same way with **`~/clawchat/friend-greeting.md`**: same rules as
58
58
  is an instruction to you, not the literal message; keep it short; no secrets; it is a
59
59
  partial override that the plugin may still append its own trailing line to). Delete or
60
60
  empty the file to restore the built-in instruction. The owner can turn this greeting off
61
- entirely in the plugin config (`friend_greeting: false` for Hermes, `friendGreeting: false`
62
- for OpenClaw); it is not something you can disable from chat.
61
+ entirely in the plugin config (`friendGreeting: false`); it is not something you can
62
+ disable from chat.
63
63
 
64
64
  When the user asks about "the greeting" without saying which, ask whether they mean the
65
65
  owner activation greeting or the new-friend greeting.
@@ -69,4 +69,5 @@ owner activation greeting or the new-friend greeting.
69
69
  - `greeting.md` affects only the **first-load** activation greeting to the owner;
70
70
  `friend-greeting.md` affects only the first message to a newly added non-owner friend.
71
71
  Neither changes later replies.
72
- - Both files are honored by both ClawChat agent runtimes (Hermes and OpenClaw).
72
+ - These are the OpenClaw files. A Hermes agent on the same machine does not read them: it
73
+ keeps its own copies inside each Hermes profile.
@@ -3,14 +3,14 @@
3
3
  "skills": {
4
4
  "openclaw": {
5
5
  "clawchat-core": {
6
- "version": "1.9.0",
6
+ "version": "1.10.0",
7
7
  "path": "openclaw/clawchat-core/SKILL.md",
8
- "sha256": "367b1df16439b084ab79430786a91dfa6b572714233678942032612f148a3e80",
9
- "bytes": 16656
8
+ "sha256": "acaf36b1e7c477a707ed615b07cc1b87770e5c205e1e89e1c2a00519e964ac25",
9
+ "bytes": 17683
10
10
  },
11
11
  "clawchat-liveware": {
12
12
  "version": "1.2.3",
13
- "path": "shared/clawchat-liveware/SKILL.md",
13
+ "path": "openclaw/clawchat-liveware/SKILL.md",
14
14
  "sha256": "2ed150bad7972d452b5b437d7c524ab24c27b0677419e590f087f50b7e7a3cdd",
15
15
  "bytes": 12591
16
16
  },
@@ -21,10 +21,10 @@
21
21
  "bytes": 8892
22
22
  },
23
23
  "clawchat-set-greeting": {
24
- "version": "1.2.0",
25
- "path": "shared/clawchat-set-greeting/SKILL.md",
26
- "sha256": "bf1bbb71d287faafa499920f91450e99c27f638e7ee67bfb209a43418937a87c",
27
- "bytes": 4184
24
+ "version": "1.2.1",
25
+ "path": "openclaw/clawchat-set-greeting/SKILL.md",
26
+ "sha256": "11d9195fa11c1d23cfe282f28f69ce899ae37bdcbfc79fecebee64f4f1a803da",
27
+ "bytes": 4195
28
28
  },
29
29
  "clawchat-liveware-sample": {
30
30
  "version": "2.0.1",
@@ -33,24 +33,24 @@
33
33
  "bytes": 13943
34
34
  },
35
35
  "clawchat-orchestration": {
36
- "version": "2.1.0",
36
+ "version": "2.2.0",
37
37
  "path": "shared/clawchat-orchestration/SKILL.md",
38
- "sha256": "501b757b1c4718b85f1a5dd2fc3a7f30a5849eebd57e6acb78ae50390d6d0722",
39
- "bytes": 11155
38
+ "sha256": "5f7319be1c77dfa265c8bce343d9d28ad52c2285667926b7fe8ba273d593d03c",
39
+ "bytes": 11700
40
40
  }
41
41
  },
42
42
  "hermes": {
43
43
  "clawchat-core": {
44
- "version": "1.16.0",
44
+ "version": "1.17.0",
45
45
  "path": "hermes/clawchat-core/SKILL.md",
46
- "sha256": "091f3e36632629d6783e06e6321ccdce98f9a17baf131f2f55c7700adb9335d5",
47
- "bytes": 24659
46
+ "sha256": "b3038b3a540645a19224ce198fdf77f1fbb109b843a3a80fe803a955d027d689",
47
+ "bytes": 27668
48
48
  },
49
49
  "clawchat-liveware": {
50
- "version": "1.2.3",
51
- "path": "shared/clawchat-liveware/SKILL.md",
52
- "sha256": "2ed150bad7972d452b5b437d7c524ab24c27b0677419e590f087f50b7e7a3cdd",
53
- "bytes": 12591
50
+ "version": "1.3.0",
51
+ "path": "hermes/clawchat-liveware/SKILL.md",
52
+ "sha256": "6bc6fff06c0694bf02323eff42926f15e7b6d6ed744e66b23a971f9290a9bb28",
53
+ "bytes": 14562
54
54
  },
55
55
  "clawchat-liveware-dev": {
56
56
  "version": "1.0.0",
@@ -59,10 +59,10 @@
59
59
  "bytes": 8892
60
60
  },
61
61
  "clawchat-set-greeting": {
62
- "version": "1.2.0",
63
- "path": "shared/clawchat-set-greeting/SKILL.md",
64
- "sha256": "bf1bbb71d287faafa499920f91450e99c27f638e7ee67bfb209a43418937a87c",
65
- "bytes": 4184
62
+ "version": "1.3.0",
63
+ "path": "hermes/clawchat-set-greeting/SKILL.md",
64
+ "sha256": "5092a2df48dfd61495bcd1f3be8f6603ca214b7f33afaddcf2d7c28442e91328",
65
+ "bytes": 5797
66
66
  },
67
67
  "clawchat-liveware-sample": {
68
68
  "version": "2.0.1",
@@ -71,10 +71,10 @@
71
71
  "bytes": 13843
72
72
  },
73
73
  "clawchat-orchestration": {
74
- "version": "2.1.0",
74
+ "version": "2.2.0",
75
75
  "path": "shared/clawchat-orchestration/SKILL.md",
76
- "sha256": "501b757b1c4718b85f1a5dd2fc3a7f30a5849eebd57e6acb78ae50390d6d0722",
77
- "bytes": 11155
76
+ "sha256": "5f7319be1c77dfa265c8bce343d9d28ad52c2285667926b7fe8ba273d593d03c",
77
+ "bytes": 11700
78
78
  }
79
79
  }
80
80
  },