@clawling/clawchat-plugin-openclaw 2026.5.12-30 → 2026.5.12-31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -48
- package/dist/src/api-client.js +37 -4
- package/dist/src/config.js +4 -0
- package/dist/src/login.runtime.js +1 -0
- package/dist/src/outbound.js +1 -0
- package/dist/src/reply-dispatcher.js +1 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/tools-schema.js +32 -0
- package/dist/src/tools.js +129 -14
- package/openclaw.plugin.json +9 -1
- package/package.json +7 -3
- package/prompts/default-group-bio.md +14 -14
- package/prompts/default-owner-behavior.md +22 -22
- package/skills/clawchat/SKILL.md +5 -1
- package/src/api-client.ts +61 -5
- package/src/api-types.ts +17 -0
- package/src/config.ts +7 -0
- package/src/login.runtime.ts +1 -0
- package/src/outbound.ts +1 -0
- package/src/reply-dispatcher.ts +1 -0
- package/src/runtime.ts +1 -0
- package/src/tools-schema.ts +47 -0
- package/src/tools.ts +169 -13
- package/INSTALL.md +0 -64
- package/dist/src/api-types.test-d.js +0 -10
- package/dist/src/protocol-types.typecheck.js +0 -1
- package/src/api-client.test.ts +0 -827
- package/src/channel.outbound.test.ts +0 -433
- package/src/channel.test.ts +0 -279
- package/src/clawchat-memory.test.ts +0 -482
- package/src/clawchat-metadata.test.ts +0 -477
- package/src/client.test.ts +0 -169
- package/src/commands.test.ts +0 -108
- package/src/config.test.ts +0 -352
- package/src/group-message-coalescer.test.ts +0 -227
- package/src/inbound.test.ts +0 -508
- package/src/llm-context-debug.test.ts +0 -55
- package/src/login.runtime.test.ts +0 -737
- package/src/manifest.test.ts +0 -360
- package/src/media-runtime.test.ts +0 -207
- package/src/message-mapper.test.ts +0 -201
- package/src/mock-transport.test.ts +0 -35
- package/src/outbound.test.ts +0 -1269
- package/src/plugin-entry.test.ts +0 -43
- package/src/plugin-prompts.test.ts +0 -94
- package/src/profile-prompt.test.ts +0 -290
- package/src/profile-sync.test.ts +0 -539
- package/src/prompt-injection.test.ts +0 -39
- package/src/protocol-types.test.ts +0 -69
- package/src/protocol-types.typecheck.ts +0 -89
- package/src/protocol.test.ts +0 -39
- package/src/reply-dispatcher.test.ts +0 -1352
- package/src/runtime.test.ts +0 -4920
- package/src/scripts.test.ts +0 -85
- package/src/storage.test.ts +0 -566
- package/src/terminal-send.test.ts +0 -81
- package/src/tools.test.ts +0 -933
- package/src/ws-alignment.test.ts +0 -103
- package/src/ws-client.test.ts +0 -1217
- package/src/ws-log.test.ts +0 -32
package/README.md
CHANGED
|
@@ -20,13 +20,10 @@ npm i @clawling/clawchat-plugin-openclaw
|
|
|
20
20
|
|
|
21
21
|
Requires `openclaw >= 2026.5.4` as a peer host.
|
|
22
22
|
|
|
23
|
-
For the OpenClaw plugin install/update flow, use the R2-hosted tarball install
|
|
24
|
-
command documented in [`INSTALL.md`](./INSTALL.md).
|
|
25
|
-
|
|
26
23
|
Example LLM prompt:
|
|
27
24
|
|
|
28
25
|
```text
|
|
29
|
-
|
|
26
|
+
Install and activate the ClawChat plugin from npm package @clawling/clawchat-plugin-openclaw. The invite code is XXXXXX.
|
|
30
27
|
```
|
|
31
28
|
|
|
32
29
|
## Current activation paths
|
|
@@ -165,7 +162,8 @@ npm run typecheck
|
|
|
165
162
|
|
|
166
163
|
Tests live next to the source they cover (`*.test.ts`). The development entrypoint stays in TypeScript for the OpenClaw extension loader, while npm installs use the compiled runtime entrypoint generated by `npm run build` / `prepack` under `dist/`.
|
|
167
164
|
|
|
168
|
-
Functional
|
|
165
|
+
Functional E2E test cases are documented in the root ClawChat workspace E2E
|
|
166
|
+
docs; keep that guide updated when adding or changing E2E flows.
|
|
169
167
|
|
|
170
168
|
For OpenClaw host SDK/source lookup while developing this plugin, optionally
|
|
171
169
|
clone OpenClaw into `tmp/openclaw`:
|
|
@@ -178,49 +176,6 @@ npm run dev:openclaw-source
|
|
|
178
176
|
This checkout is local-only. It is ignored by git and is not required to run the
|
|
179
177
|
plugin tests or publish the package.
|
|
180
178
|
|
|
181
|
-
## R2 package scripts
|
|
182
|
-
|
|
183
|
-
Create and upload the OpenClaw plugin tarball to the R2 `openclaw/` prefix:
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
./scripts/package_openclaw_plugin.sh
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
The script runs `npm pack`, removes `devDependencies` from the generated `.tgz`
|
|
190
|
-
metadata so OpenClaw installs only runtime dependencies, uploads the `.tgz` to
|
|
191
|
-
the configured R2 bucket, updates the `latest` R2 alias, uploads `INSTALL.md` as
|
|
192
|
-
`openclaw/install.md`, and prints the public URLs. R2 credentials are read from
|
|
193
|
-
`scripts/.env.r2`, which is ignored by git. Copy `scripts/.env.r2.example` to
|
|
194
|
-
`scripts/.env.r2` and fill in the credentials. Use `--no-upload` to build the
|
|
195
|
-
tarball without uploading it.
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
AWS_ACCESS_KEY_ID=...
|
|
199
|
-
AWS_SECRET_ACCESS_KEY=...
|
|
200
|
-
AWS_DEFAULT_REGION=auto
|
|
201
|
-
R2_ENDPOINT=https://...
|
|
202
|
-
R2_BUCKET=...
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Install the R2-hosted latest tarball on a device or container with OpenClaw
|
|
206
|
-
available:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
./scripts/install_openclaw.sh
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
To install a specific uploaded version, pass the version string:
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
./scripts/install_openclaw.sh 2026.5.16-1
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
To install a specific uploaded tarball URL, pass its URL explicitly:
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
./scripts/install_openclaw.sh https://plugin.clawling.chat/openclaw/newbase-clawchat-clawchat-plugin-openclaw-2026.5.16-1.tgz
|
|
222
|
-
```
|
|
223
|
-
|
|
224
179
|
## License
|
|
225
180
|
|
|
226
181
|
See the repository root.
|
package/dist/src/api-client.js
CHANGED
|
@@ -5,9 +5,16 @@ export function createOpenclawClawlingApiClient(opts) {
|
|
|
5
5
|
throw new ClawlingApiError("validation", `clawchat-plugin-openclaw baseUrl must start with http:// or https:// (got "${opts.baseUrl}")`);
|
|
6
6
|
}
|
|
7
7
|
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
8
|
+
let mediaBaseUrl = baseUrl;
|
|
9
|
+
if (opts.mediaBaseUrl && opts.mediaBaseUrl.trim()) {
|
|
10
|
+
if (!/^https?:\/\//i.test(opts.mediaBaseUrl)) {
|
|
11
|
+
throw new ClawlingApiError("validation", `clawchat-plugin-openclaw mediaBaseUrl must start with http:// or https:// (got "${opts.mediaBaseUrl}")`);
|
|
12
|
+
}
|
|
13
|
+
mediaBaseUrl = opts.mediaBaseUrl.replace(/\/+$/, "");
|
|
14
|
+
}
|
|
8
15
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
9
|
-
function url(path) {
|
|
10
|
-
return `${
|
|
16
|
+
function url(path, base = baseUrl) {
|
|
17
|
+
return `${base}${path}`;
|
|
11
18
|
}
|
|
12
19
|
function authHeaders(extra = {}) {
|
|
13
20
|
// `X-Device-Id` is sent on every request so the server can correlate
|
|
@@ -75,7 +82,7 @@ export function createOpenclawClawlingApiClient(opts) {
|
|
|
75
82
|
if (init?.body !== undefined) {
|
|
76
83
|
requestInit.body = init.body;
|
|
77
84
|
}
|
|
78
|
-
res = await fetchImpl(url(path), requestInit);
|
|
85
|
+
res = await fetchImpl(url(path, init?.baseUrl || undefined), requestInit);
|
|
79
86
|
}
|
|
80
87
|
catch (err) {
|
|
81
88
|
throw new ClawlingApiError("transport", `fetch failed: ${err instanceof Error ? err.message : String(err)}`, { path });
|
|
@@ -148,6 +155,32 @@ export function createOpenclawClawlingApiClient(opts) {
|
|
|
148
155
|
async listFriends() {
|
|
149
156
|
return await call("GET", "/v1/friendships");
|
|
150
157
|
},
|
|
158
|
+
async sendFriendRequest(params) {
|
|
159
|
+
assertNonBlankId(params.userId, "sendFriendRequest: userId");
|
|
160
|
+
return await call("POST", "/v1/friendships", {
|
|
161
|
+
body: JSON.stringify({
|
|
162
|
+
user_id: params.userId,
|
|
163
|
+
...(typeof params.greeting === "string" ? { greeting: params.greeting } : {}),
|
|
164
|
+
}),
|
|
165
|
+
headers: { "content-type": "application/json" },
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
async listFriendRequests(params) {
|
|
169
|
+
if (params.direction !== "incoming" && params.direction !== "outgoing") {
|
|
170
|
+
throw new ClawlingApiError("validation", "listFriendRequests: direction must be incoming or outgoing");
|
|
171
|
+
}
|
|
172
|
+
return await call("GET", `/v1/friendships/requests/${params.direction}`);
|
|
173
|
+
},
|
|
174
|
+
async acceptFriendRequest(requestId) {
|
|
175
|
+
return await call("POST", `/v1/friendships/requests/${encodeURIComponent(String(requestId))}/accept`);
|
|
176
|
+
},
|
|
177
|
+
async rejectFriendRequest(requestId) {
|
|
178
|
+
return await call("POST", `/v1/friendships/requests/${encodeURIComponent(String(requestId))}/reject`);
|
|
179
|
+
},
|
|
180
|
+
async removeFriend(friendUserId) {
|
|
181
|
+
assertNonBlankId(friendUserId, "removeFriend: friendUserId");
|
|
182
|
+
return await call("DELETE", `/v1/friendships/${encodeURIComponent(friendUserId)}`);
|
|
183
|
+
},
|
|
151
184
|
async searchUsers(params) {
|
|
152
185
|
const sp = new URLSearchParams();
|
|
153
186
|
if (typeof params.q === "string")
|
|
@@ -245,7 +278,7 @@ export function createOpenclawClawlingApiClient(opts) {
|
|
|
245
278
|
});
|
|
246
279
|
const fd = new FormData();
|
|
247
280
|
fd.set("file", file);
|
|
248
|
-
const data = await call("POST", "/media/upload", { body: fd });
|
|
281
|
+
const data = await call("POST", "/media/upload", { body: fd, baseUrl: mediaBaseUrl });
|
|
249
282
|
return parseUploadResult(data, "/media/upload");
|
|
250
283
|
},
|
|
251
284
|
async uploadAvatar(params) {
|
package/dist/src/config.js
CHANGED
|
@@ -7,6 +7,7 @@ export const CLAWCHAT_OWNER_USER_ID_ENV = "CLAWCHAT_OWNER_USER_ID";
|
|
|
7
7
|
export const CLAWCHAT_REFRESH_TOKEN_ENV = "CLAWCHAT_REFRESH_TOKEN";
|
|
8
8
|
export const CLAWCHAT_BASE_URL_ENV = "CLAWCHAT_BASE_URL";
|
|
9
9
|
export const CLAWCHAT_WEBSOCKET_URL_ENV = "CLAWCHAT_WEBSOCKET_URL";
|
|
10
|
+
export const CLAWCHAT_MEDIA_BASE_URL_ENV = "CLAWCHAT_MEDIA_BASE_URL";
|
|
10
11
|
/**
|
|
11
12
|
* Built-in defaults for the Clawling Chat endpoints so `openclaw channel
|
|
12
13
|
* login` works out of the box without requiring a prior `openclaw channel
|
|
@@ -46,6 +47,7 @@ export const openclawClawlingConfigSchema = {
|
|
|
46
47
|
enabled: { type: "boolean" },
|
|
47
48
|
websocketUrl: { type: "string" },
|
|
48
49
|
baseUrl: { type: "string" },
|
|
50
|
+
mediaBaseUrl: { type: "string" },
|
|
49
51
|
token: { type: "string" },
|
|
50
52
|
refreshToken: { type: "string" },
|
|
51
53
|
agentId: { type: "string" },
|
|
@@ -253,6 +255,7 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
253
255
|
const baseUrl = readOptionalString(channel.baseUrl) ||
|
|
254
256
|
readEnvString(env, CLAWCHAT_BASE_URL_ENV) ||
|
|
255
257
|
DEFAULT_BASE_URL;
|
|
258
|
+
const mediaBaseUrl = readOptionalString(channel.mediaBaseUrl) || readEnvString(env, CLAWCHAT_MEDIA_BASE_URL_ENV);
|
|
256
259
|
const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
|
|
257
260
|
const agentId = readOptionalString(channel.agentId) || readEnvString(env, CLAWCHAT_AGENT_ID_ENV);
|
|
258
261
|
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
@@ -276,6 +279,7 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
276
279
|
}),
|
|
277
280
|
websocketUrl,
|
|
278
281
|
baseUrl,
|
|
282
|
+
mediaBaseUrl,
|
|
279
283
|
token,
|
|
280
284
|
agentId,
|
|
281
285
|
userId,
|
|
@@ -131,6 +131,7 @@ export async function runOpenclawClawlingLogin(params) {
|
|
|
131
131
|
}
|
|
132
132
|
const apiClient = (params.apiClientFactory ?? createOpenclawClawlingApiClient)({
|
|
133
133
|
baseUrl: account.baseUrl,
|
|
134
|
+
mediaBaseUrl: account.mediaBaseUrl,
|
|
134
135
|
// Pre-login we may not have a token yet. Send the current one (or empty)
|
|
135
136
|
// — the server should accept an unauthenticated invite-code exchange.
|
|
136
137
|
token: account.token || "",
|
package/dist/src/outbound.js
CHANGED
|
@@ -581,6 +581,7 @@ export const openclawClawlingOutbound = {
|
|
|
581
581
|
const runtime = getOpenclawClawlingRuntime();
|
|
582
582
|
const apiClient = createOpenclawClawlingApiClient({
|
|
583
583
|
baseUrl: account.baseUrl,
|
|
584
|
+
mediaBaseUrl: account.mediaBaseUrl,
|
|
584
585
|
token: account.token,
|
|
585
586
|
userId: account.userId,
|
|
586
587
|
});
|
|
@@ -125,6 +125,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
125
125
|
return null;
|
|
126
126
|
return createOpenclawClawlingApiClient({
|
|
127
127
|
baseUrl: account.baseUrl,
|
|
128
|
+
mediaBaseUrl: account.mediaBaseUrl,
|
|
128
129
|
token: account.token,
|
|
129
130
|
userId: account.userId,
|
|
130
131
|
});
|
package/dist/src/runtime.js
CHANGED
|
@@ -364,6 +364,7 @@ export async function startOpenclawClawlingGateway(params) {
|
|
|
364
364
|
const getConversationApiClient = () => {
|
|
365
365
|
conversationApiClient ??= createOpenclawClawlingApiClient({
|
|
366
366
|
baseUrl: account.baseUrl,
|
|
367
|
+
mediaBaseUrl: account.mediaBaseUrl,
|
|
367
368
|
token: account.token,
|
|
368
369
|
userId: account.userId,
|
|
369
370
|
});
|
package/dist/src/tools-schema.js
CHANGED
|
@@ -76,6 +76,38 @@ export const ClawchatGetUserProfileSchema = Type.Object({
|
|
|
76
76
|
}),
|
|
77
77
|
});
|
|
78
78
|
export const ClawchatListAccountFriendsSchema = Type.Object({});
|
|
79
|
+
export const ClawchatSendFriendRequestSchema = Type.Object({
|
|
80
|
+
userId: Type.String({
|
|
81
|
+
minLength: 1,
|
|
82
|
+
description: "Explicit target ClawChat user id to send a friend request to.",
|
|
83
|
+
}),
|
|
84
|
+
greeting: Type.Optional(Type.String({
|
|
85
|
+
description: "Optional greeting/message included with the friend request.",
|
|
86
|
+
})),
|
|
87
|
+
});
|
|
88
|
+
export const ClawchatListFriendRequestsSchema = Type.Object({
|
|
89
|
+
direction: Type.Optional(Type.Union([Type.Literal("incoming"), Type.Literal("outgoing")], {
|
|
90
|
+
description: "incoming lists requests sent to the connected account; outgoing lists requests sent by the connected account. Defaults to incoming.",
|
|
91
|
+
})),
|
|
92
|
+
});
|
|
93
|
+
export const ClawchatAcceptFriendRequestSchema = Type.Object({
|
|
94
|
+
requestId: Type.Integer({
|
|
95
|
+
minimum: 1,
|
|
96
|
+
description: "Concrete friend request id to accept.",
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
export const ClawchatRejectFriendRequestSchema = Type.Object({
|
|
100
|
+
requestId: Type.Integer({
|
|
101
|
+
minimum: 1,
|
|
102
|
+
description: "Concrete friend request id to reject.",
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
export const ClawchatRemoveFriendSchema = Type.Object({
|
|
106
|
+
friendUserId: Type.String({
|
|
107
|
+
minLength: 1,
|
|
108
|
+
description: "Explicit ClawChat user id of the accepted friend to remove.",
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
79
111
|
export const ClawchatSearchUsersSchema = Type.Object({
|
|
80
112
|
q: Type.Optional(Type.String({
|
|
81
113
|
description: "Search query for ClawChat username or nickname",
|
package/dist/src/tools.js
CHANGED
|
@@ -9,9 +9,50 @@ import { getOpenclawClawlingClient, } from "./runtime.js";
|
|
|
9
9
|
import { markTerminalClawChatSend } from "./terminal-send.js";
|
|
10
10
|
import { editClawChatMemoryBody, readClawChatMemoryFile, resolveClawChatMemoryPath, searchClawChatMemory, writeClawChatMemoryBody, } from "./clawchat-memory.js";
|
|
11
11
|
import { pullGroupMetadata, pullOwnerMetadata, pullUserMetadata, pushMetadata, updateMetadata, } from "./clawchat-metadata.js";
|
|
12
|
-
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatSearchUsersSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatUploadMediaFileSchema, } from "./tools-schema.js";
|
|
12
|
+
import { ClawchatGetAccountProfileSchema, ClawchatGetConversationSchema, ClawchatGetUserProfileSchema, ClawchatAcceptFriendRequestSchema, ClawchatMemoryEditSchema, ClawchatMemoryReadSchema, ClawchatMemorySearchSchema, ClawchatMemoryWriteSchema, ClawchatMetadataSyncSchema, ClawchatMetadataUpdateSchema, ClawchatCreateMomentCommentSchema, ClawchatCreateMomentSchema, ClawchatDeleteMomentCommentSchema, ClawchatDeleteMomentSchema, ClawchatListAccountFriendsSchema, ClawchatListFriendRequestsSchema, ClawchatListMomentsSchema, ClawchatMentionMessageSchema, ClawchatReplyMomentCommentSchema, ClawchatRejectFriendRequestSchema, ClawchatRemoveFriendSchema, ClawchatSearchUsersSchema, ClawchatSendFriendRequestSchema, ClawchatToggleMomentReactionSchema, ClawchatUpdateAccountProfileSchema, ClawchatUploadAvatarImageSchema, ClawchatUploadMediaFileSchema, } from "./tools-schema.js";
|
|
13
13
|
const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
14
|
-
|
|
14
|
+
// Owner-approval gate business codes (must match member-backend codes.go).
|
|
15
|
+
const CODE_PENDING_APPROVAL = 21001;
|
|
16
|
+
const CODE_POLICY_FORBIDDEN = 21003;
|
|
17
|
+
function extractRequestId(err) {
|
|
18
|
+
// Defensive: request_id may ride in meta.data when the envelope carried it.
|
|
19
|
+
const data = err.meta?.data;
|
|
20
|
+
if (data && typeof data === "object") {
|
|
21
|
+
const v = data.request_id;
|
|
22
|
+
if (typeof v === "string" && v.length > 0)
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function permissionGateResult(err) {
|
|
28
|
+
const code = err.meta?.code;
|
|
29
|
+
const requestId = extractRequestId(err);
|
|
30
|
+
let message;
|
|
31
|
+
let status;
|
|
32
|
+
if (code === CODE_PENDING_APPROVAL) {
|
|
33
|
+
message =
|
|
34
|
+
"This operation requires the owner's approval and has been submitted for review" +
|
|
35
|
+
(requestId ? ` (request_id=${requestId})` : "") +
|
|
36
|
+
". It has NOT failed. Do not retry — the result will arrive later as a normal " +
|
|
37
|
+
"chat message; wait for it instead of calling this tool again.";
|
|
38
|
+
status = "pending";
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
message =
|
|
42
|
+
"This operation is blocked by the owner's policy (policy_forbidden) and was not " +
|
|
43
|
+
"performed. Do not retry — the owner must change the policy before it can succeed.";
|
|
44
|
+
status = "forbidden";
|
|
45
|
+
}
|
|
46
|
+
return jsonResponse({
|
|
47
|
+
error: "permission",
|
|
48
|
+
message,
|
|
49
|
+
retryable: false,
|
|
50
|
+
status,
|
|
51
|
+
...(requestId ? { request_id: requestId } : {}),
|
|
52
|
+
...(err.meta ? { meta: err.meta } : {}),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function jsonResponse(data) {
|
|
15
56
|
return {
|
|
16
57
|
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
17
58
|
details: data,
|
|
@@ -20,7 +61,11 @@ function jsonResponse(data) {
|
|
|
20
61
|
function configError(message) {
|
|
21
62
|
return jsonResponse({ error: "config", message });
|
|
22
63
|
}
|
|
23
|
-
function apiError(err) {
|
|
64
|
+
export function apiError(err) {
|
|
65
|
+
const code = err.meta?.code;
|
|
66
|
+
if (code === CODE_PENDING_APPROVAL || code === CODE_POLICY_FORBIDDEN) {
|
|
67
|
+
return permissionGateResult(err);
|
|
68
|
+
}
|
|
24
69
|
return jsonResponse({
|
|
25
70
|
error: err.kind,
|
|
26
71
|
message: err.message,
|
|
@@ -548,7 +593,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
548
593
|
"This profile is the platform-side mirror of the local assistant identity; if fields are missing, report them as unset instead of inventing values. " +
|
|
549
594
|
"TRIGGER — invoke when the user asks for the ClawChat account/profile connected to this agent, " +
|
|
550
595
|
"such as 'show my ClawChat profile', 'what is the configured ClawChat account?', " +
|
|
551
|
-
"'
|
|
596
|
+
"'show the current ClawChat account details', or 'show the ClawChat nickname, avatar, and bio'. " +
|
|
552
597
|
"Do not frame this as a human user's personal account."),
|
|
553
598
|
parameters: ClawchatGetAccountProfileSchema,
|
|
554
599
|
async execute(_callId, params) {
|
|
@@ -583,11 +628,81 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
583
628
|
return await recordClawchatToolCall("clawchat_list_account_friends", params, async () => withClient((c) => c.listFriends()));
|
|
584
629
|
},
|
|
585
630
|
}, { name: "clawchat_list_account_friends" });
|
|
631
|
+
api.registerTool({
|
|
632
|
+
name: "clawchat_send_friend_request",
|
|
633
|
+
label: "Send ClawChat Friend Request",
|
|
634
|
+
description: toolDescription("Send a friend request from the connected ClawChat account to a specific user by explicit userId. " +
|
|
635
|
+
"TRIGGER - invoke when the user asks to add, friend, connect with, or send a friend request to a ClawChat user and provides a concrete userId, or after clawchat_search_users returns a selected userId. " +
|
|
636
|
+
"Do not guess or infer userId from nickname, display name, alias, or local memory text; search first when needed."),
|
|
637
|
+
parameters: ClawchatSendFriendRequestSchema,
|
|
638
|
+
async execute(_callId, params) {
|
|
639
|
+
return await recordClawchatToolCall("clawchat_send_friend_request", params, async () => {
|
|
640
|
+
const p = params;
|
|
641
|
+
return await withClient((c) => c.sendFriendRequest(p));
|
|
642
|
+
});
|
|
643
|
+
},
|
|
644
|
+
}, { name: "clawchat_send_friend_request" });
|
|
645
|
+
api.registerTool({
|
|
646
|
+
name: "clawchat_list_friend_requests",
|
|
647
|
+
label: "List ClawChat Friend Requests",
|
|
648
|
+
description: toolDescription("List pending friend requests for the connected ClawChat account. " +
|
|
649
|
+
"Use direction='incoming' for requests other users sent to this account, and direction='outgoing' for requests this account sent to other users. Defaults to incoming. " +
|
|
650
|
+
"TRIGGER - invoke when the user asks to view, check, review, or list friend requests, pending requests, incoming requests, or outgoing requests."),
|
|
651
|
+
parameters: ClawchatListFriendRequestsSchema,
|
|
652
|
+
async execute(_callId, params) {
|
|
653
|
+
return await recordClawchatToolCall("clawchat_list_friend_requests", params, async () => {
|
|
654
|
+
const p = (params ?? {});
|
|
655
|
+
return await withClient((c) => c.listFriendRequests({ direction: p.direction ?? "incoming" }));
|
|
656
|
+
});
|
|
657
|
+
},
|
|
658
|
+
}, { name: "clawchat_list_friend_requests" });
|
|
659
|
+
api.registerTool({
|
|
660
|
+
name: "clawchat_accept_friend_request",
|
|
661
|
+
label: "Accept ClawChat Friend Request",
|
|
662
|
+
description: toolDescription("Accept one pending incoming friend request by exact requestId. " +
|
|
663
|
+
"TRIGGER - invoke when the user asks to accept, approve, or pass a friend request and provides or selects a concrete requestId returned by clawchat_list_friend_requests. " +
|
|
664
|
+
"List incoming requests first when the target is ambiguous."),
|
|
665
|
+
parameters: ClawchatAcceptFriendRequestSchema,
|
|
666
|
+
async execute(_callId, params) {
|
|
667
|
+
return await recordClawchatToolCall("clawchat_accept_friend_request", params, async () => {
|
|
668
|
+
const p = params;
|
|
669
|
+
return await withClient((c) => c.acceptFriendRequest(p.requestId));
|
|
670
|
+
});
|
|
671
|
+
},
|
|
672
|
+
}, { name: "clawchat_accept_friend_request" });
|
|
673
|
+
api.registerTool({
|
|
674
|
+
name: "clawchat_reject_friend_request",
|
|
675
|
+
label: "Reject ClawChat Friend Request",
|
|
676
|
+
description: toolDescription("Reject one pending incoming friend request by exact requestId. " +
|
|
677
|
+
"TRIGGER - invoke when the user asks to reject, decline, refuse, or ignore a friend request and provides or selects a concrete requestId returned by clawchat_list_friend_requests. " +
|
|
678
|
+
"List incoming requests first when the target is ambiguous."),
|
|
679
|
+
parameters: ClawchatRejectFriendRequestSchema,
|
|
680
|
+
async execute(_callId, params) {
|
|
681
|
+
return await recordClawchatToolCall("clawchat_reject_friend_request", params, async () => {
|
|
682
|
+
const p = params;
|
|
683
|
+
return await withClient((c) => c.rejectFriendRequest(p.requestId));
|
|
684
|
+
});
|
|
685
|
+
},
|
|
686
|
+
}, { name: "clawchat_reject_friend_request" });
|
|
687
|
+
api.registerTool({
|
|
688
|
+
name: "clawchat_remove_friend",
|
|
689
|
+
label: "Remove ClawChat Friend",
|
|
690
|
+
description: toolDescription("Remove an accepted friend from the connected ClawChat account by explicit friendUserId. " +
|
|
691
|
+
"TRIGGER - invoke when the user asks to delete, remove, unfriend, or disconnect an existing ClawChat friend and provides a concrete friendUserId. " +
|
|
692
|
+
"Use clawchat_list_account_friends first when the target is ambiguous; do not guess ids from names."),
|
|
693
|
+
parameters: ClawchatRemoveFriendSchema,
|
|
694
|
+
async execute(_callId, params) {
|
|
695
|
+
return await recordClawchatToolCall("clawchat_remove_friend", params, async () => {
|
|
696
|
+
const p = params;
|
|
697
|
+
return await withClient((c) => c.removeFriend(p.friendUserId));
|
|
698
|
+
});
|
|
699
|
+
},
|
|
700
|
+
}, { name: "clawchat_remove_friend" });
|
|
586
701
|
api.registerTool({
|
|
587
702
|
name: "clawchat_search_users",
|
|
588
703
|
label: "Search ClawChat Users",
|
|
589
704
|
description: toolDescription("Search ClawChat users by username or nickname. Search server-side ClawChat users in the ClawChat user directory. " +
|
|
590
|
-
"TRIGGER - invoke when the user asks to search, find, or look up ClawChat users in the server directory by a typed query, username, or public nickname, such as \"search ClawChat users named Alice\", \"
|
|
705
|
+
"TRIGGER - invoke when the user asks to search, find, or look up ClawChat users in the server directory by a typed query, username, or public nickname, such as \"search ClawChat users named Alice\", \"find ClawChat user Alice\", or \"look up nickname Alice\". " +
|
|
591
706
|
"This does not search local ClawChat memory files, aliases, known_as notes, relationship notes, group notes, or agent-authored Markdown memory. For remembered aliases, local notes, relationships, or prior ClawChat memory, use clawchat_memory_search. " +
|
|
592
707
|
"Empty q returns no users. Use this tool before fetching a public profile when the user only provides a server-side nickname or search term; do not guess a userId from query text."),
|
|
593
708
|
parameters: ClawchatSearchUsersSchema,
|
|
@@ -696,7 +811,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
696
811
|
name: "clawchat_list_moments",
|
|
697
812
|
label: "List ClawChat Moments",
|
|
698
813
|
description: toolDescription("List the configured ClawChat account's visible moments feed, including moments from the account and its friends. " +
|
|
699
|
-
"TRIGGER - invoke when the user asks to view, browse, refresh, or paginate ClawChat moments/dynamics/feed, such as \"show my ClawChat moments\", \"
|
|
814
|
+
"TRIGGER - invoke when the user asks to view, browse, refresh, or paginate ClawChat moments/dynamics/feed, such as \"show my ClawChat moments\", \"view dynamics\", \"show friend moments\", or \"more moments\". " +
|
|
700
815
|
"Use before/comment/reaction/delete actions when the user needs to choose a moment id. This is a friends-only feed endpoint, not a global public timeline."),
|
|
701
816
|
parameters: ClawchatListMomentsSchema,
|
|
702
817
|
async execute(_callId, params) {
|
|
@@ -713,7 +828,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
713
828
|
name: "clawchat_create_moment",
|
|
714
829
|
label: "Create ClawChat Moment",
|
|
715
830
|
description: toolDescription("Create a new ClawChat moment/dynamic for the configured ClawChat account. " +
|
|
716
|
-
"TRIGGER - invoke when the user asks to publish, post, or send a ClawChat moment/dynamic, such as \"post a ClawChat moment saying ...\", \"
|
|
831
|
+
"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 ...\". " +
|
|
717
832
|
"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."),
|
|
718
833
|
parameters: ClawchatCreateMomentSchema,
|
|
719
834
|
async execute(_callId, params) {
|
|
@@ -749,7 +864,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
749
864
|
name: "clawchat_toggle_moment_reaction",
|
|
750
865
|
label: "Toggle ClawChat Moment Reaction",
|
|
751
866
|
description: toolDescription("Toggle an emoji reaction on a ClawChat moment. " +
|
|
752
|
-
"TRIGGER - invoke when the user asks to react, like, unlike, emoji-react, or remove the same emoji reaction on a specific ClawChat moment, such as \"like moment 123 with
|
|
867
|
+
"TRIGGER - invoke when the user asks to react, like, unlike, emoji-react, or remove the same emoji reaction on a specific ClawChat moment, such as \"like moment 123 with thumbs up\", \"add a thumbs-up reaction to moment 123\", or \"remove this reaction\". " +
|
|
753
868
|
"The API adds the reaction if missing and removes it if already present. Require a concrete moment id and emoji."),
|
|
754
869
|
parameters: ClawchatToggleMomentReactionSchema,
|
|
755
870
|
async execute(_callId, params) {
|
|
@@ -766,7 +881,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
766
881
|
name: "clawchat_create_moment_comment",
|
|
767
882
|
label: "Create ClawChat Moment Comment",
|
|
768
883
|
description: toolDescription("Create a top-level comment on a ClawChat moment. " +
|
|
769
|
-
"TRIGGER - invoke when the user asks to comment/reply directly to a moment/dynamic, not to another comment, such as \"comment on moment 123: ...\", \"
|
|
884
|
+
"TRIGGER - invoke when the user asks to comment/reply directly to a moment/dynamic, not to another comment, such as \"comment on moment 123: ...\", \"leave a comment on dynamic 123 ...\", or \"write under this moment ...\". " +
|
|
770
885
|
"Require a concrete moment id and non-empty text. Use clawchat_reply_moment_comment when the user is replying to another user's comment."),
|
|
771
886
|
parameters: ClawchatCreateMomentCommentSchema,
|
|
772
887
|
async execute(_callId, params) {
|
|
@@ -783,7 +898,7 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
783
898
|
name: "clawchat_reply_moment_comment",
|
|
784
899
|
label: "Reply To ClawChat Moment Comment",
|
|
785
900
|
description: toolDescription("Reply to an existing ClawChat moment comment with a single-level reply. " +
|
|
786
|
-
"TRIGGER - invoke when the user asks to reply to another user's comment on a moment/dynamic, such as \"reply to comment 456 on moment 123: ...\", \"
|
|
901
|
+
"TRIGGER - invoke when the user asks to reply to another user's comment on a moment/dynamic, such as \"reply to comment 456 on moment 123: ...\", \"reply to comment 456 ...\", or \"reply to that comment ...\". " +
|
|
787
902
|
"Require concrete moment and comment ids; do not use this for top-level comments."),
|
|
788
903
|
parameters: ClawchatReplyMomentCommentSchema,
|
|
789
904
|
async execute(_callId, params) {
|
|
@@ -820,14 +935,14 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
820
935
|
description: toolDescription("Update nickname/avatar_url/bio on the agent's connected ClawChat account (the configured ClawChat account), which mirrors the local assistant identity. " +
|
|
821
936
|
"TRIGGER — invoke this tool whenever the user's message asks to change the ClawChat account profile or local assistant name/profile while ClawChat is connected: " +
|
|
822
937
|
"(1) ClawChat account nickname/name change: 'change the ClawChat account nickname to X', " +
|
|
823
|
-
"'set this assistant name to X', '
|
|
938
|
+
"'set this assistant name to X', 'change the assistant nickname to X', 'set the account name to X' " +
|
|
824
939
|
"→ call with `nickname = X`; " +
|
|
825
940
|
"(2) ClawChat account avatar/profile-picture change: 'change the ClawChat account avatar', " +
|
|
826
|
-
"'use this image as the assistant profile picture', '
|
|
941
|
+
"'use this image as the assistant profile picture', 'change the account avatar to this image' " +
|
|
827
942
|
"→ first obtain the avatar URL (upload via `clawchat_upload_avatar_image`, OR use a provided URL directly), " +
|
|
828
943
|
"then call this tool with `avatar_url = <url>`; " +
|
|
829
944
|
"(3) ClawChat account bio/self-introduction change: 'update the ClawChat bio', " +
|
|
830
|
-
"'set the assistant self-introduction to X', '
|
|
945
|
+
"'set the assistant self-introduction to X', 'change the account bio to X' " +
|
|
831
946
|
"→ call with `bio = X`. " +
|
|
832
947
|
"You can pass `nickname`, `avatar_url`, and `bio` together in one call, or just one of them. " +
|
|
833
948
|
"At least one of the three must be present. Do not frame this as updating a human user's personal account."),
|
|
@@ -916,5 +1031,5 @@ export function registerOpenclawClawlingTools(api, options = {}) {
|
|
|
916
1031
|
});
|
|
917
1032
|
},
|
|
918
1033
|
}, { name: "clawchat_upload_media_file" });
|
|
919
|
-
api.logger.debug?.("clawchat-plugin-openclaw: registered
|
|
1034
|
+
api.logger.debug?.("clawchat-plugin-openclaw: registered 27 clawchat_* tools (get_account_profile, get_user_profile, list_account_friends, send_friend_request, list_friend_requests, accept_friend_request, reject_friend_request, remove_friend, search_users, get_conversation, mention_message, 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_search, memory_read, memory_write, memory_edit, metadata_sync, metadata_update)");
|
|
920
1035
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"CLAWCHAT_OWNER_USER_ID",
|
|
17
17
|
"CLAWCHAT_REFRESH_TOKEN",
|
|
18
18
|
"CLAWCHAT_BASE_URL",
|
|
19
|
-
"CLAWCHAT_WEBSOCKET_URL"
|
|
19
|
+
"CLAWCHAT_WEBSOCKET_URL",
|
|
20
|
+
"CLAWCHAT_MEDIA_BASE_URL"
|
|
20
21
|
]
|
|
21
22
|
},
|
|
22
23
|
"commandAliases": [
|
|
@@ -28,6 +29,11 @@
|
|
|
28
29
|
"clawchat_get_account_profile",
|
|
29
30
|
"clawchat_get_user_profile",
|
|
30
31
|
"clawchat_list_account_friends",
|
|
32
|
+
"clawchat_send_friend_request",
|
|
33
|
+
"clawchat_list_friend_requests",
|
|
34
|
+
"clawchat_accept_friend_request",
|
|
35
|
+
"clawchat_reject_friend_request",
|
|
36
|
+
"clawchat_remove_friend",
|
|
31
37
|
"clawchat_search_users",
|
|
32
38
|
"clawchat_mention_message",
|
|
33
39
|
"clawchat_get_conversation",
|
|
@@ -56,6 +62,7 @@
|
|
|
56
62
|
"enabled": { "type": "boolean" },
|
|
57
63
|
"websocketUrl": { "type": "string" },
|
|
58
64
|
"baseUrl": { "type": "string" },
|
|
65
|
+
"mediaBaseUrl": { "type": "string" },
|
|
59
66
|
"token": { "type": "string" },
|
|
60
67
|
"refreshToken": { "type": "string" },
|
|
61
68
|
"agentId": { "type": "string" },
|
|
@@ -128,6 +135,7 @@
|
|
|
128
135
|
"enabled": { "type": "boolean" },
|
|
129
136
|
"websocketUrl": { "type": "string" },
|
|
130
137
|
"baseUrl": { "type": "string" },
|
|
138
|
+
"mediaBaseUrl": { "type": "string" },
|
|
131
139
|
"token": { "type": "string" },
|
|
132
140
|
"refreshToken": { "type": "string" },
|
|
133
141
|
"agentId": { "type": "string" },
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawling/clawchat-plugin-openclaw",
|
|
3
|
-
"version": "2026.5.12-
|
|
3
|
+
"version": "2026.5.12-31",
|
|
4
4
|
"description": "OpenClaw ClawChat channel plugin",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
|
+
"!dist/**/*.test.js",
|
|
9
|
+
"!dist/**/*.test-d.js",
|
|
10
|
+
"!dist/**/*.typecheck.js",
|
|
8
11
|
"index.ts",
|
|
9
12
|
"setup-entry.ts",
|
|
10
|
-
"src",
|
|
13
|
+
"src/**/*.ts",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"!src/**/*.typecheck.ts",
|
|
11
16
|
"prompts",
|
|
12
17
|
"skills",
|
|
13
18
|
"openclaw.plugin.json",
|
|
14
|
-
"INSTALL.md",
|
|
15
19
|
"README.md"
|
|
16
20
|
],
|
|
17
21
|
"type": "module",
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
##
|
|
1
|
+
## What Kind Of Group This Is
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
-
|
|
5
|
-
-
|
|
3
|
+
- Until more specific guidance is available, treat this as an ordinary social group in the owner's circle.
|
|
4
|
+
- Group members may chat naturally, share updates, discuss ideas, coordinate timing, or move small plans forward.
|
|
5
|
+
- Infer the group's tone from how members speak to each other instead of assuming it is a meeting, ticket queue, or task space.
|
|
6
6
|
|
|
7
|
-
## Agent
|
|
7
|
+
## How The Agent Should Participate
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
9
|
+
- Reply briefly and directly when mentioned, explicitly asked, or when you can add useful information.
|
|
10
|
+
- Answer clear factual, background, or option-setting questions when you can.
|
|
11
|
+
- When a multi-person discussion becomes scattered, lightly summarize the current topic, agreements, or open questions.
|
|
12
|
+
- Otherwise, prefer listening; the default reply density for this group is low, and most messages do not need you.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## What To Be Especially Careful About
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- Do not bring private details from direct chats, other groups, or owner memory about a member into this group.
|
|
17
|
+
- Do not reply to other bots or agents in the group, even if they mention you.
|
|
18
|
+
- Do not take public-facing action for the owner in or about this group unless the owner explicitly agrees.
|
|
19
|
+
- Do not force casual conversation into task tracking or take over a topic when members have not asked for that.
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
##
|
|
1
|
+
## What You Should Aim For
|
|
2
2
|
|
|
3
|
-
-
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- owner
|
|
3
|
+
- Be present in conversation with warmth and attention.
|
|
4
|
+
- Treat people in the owner's circle as specific people, not as tasks to process:
|
|
5
|
+
- Pick up on what mattered to them last time.
|
|
6
|
+
- Continue relationships that have already been established.
|
|
7
|
+
- Avoid making every interaction feel like it starts from zero.
|
|
8
|
+
- Remember preferences the owner has already expressed, and follow them without making the owner repeat the same request.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Where You Have Room To Act
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
12
|
+
- In the current direct chat, respond naturally and maintain relationship context without asking the owner about every small step.
|
|
13
|
+
- When someone asks a factual, contextual, option-setting, or organizing question, help clearly and concisely.
|
|
14
|
+
- In group chats, listen by default; speak when addressed, explicitly asked, or when you can materially help move the discussion forward.
|
|
15
|
+
- Build and maintain social memory for the people you meet while using that memory only in the appropriate context.
|
|
16
16
|
|
|
17
|
-
##
|
|
17
|
+
## What You Should Avoid
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
19
|
+
- Do not take outward-facing action for the owner unless the owner explicitly agrees, for example:
|
|
20
|
+
- Making promises or commitments.
|
|
21
|
+
- Inviting people or adding them to groups.
|
|
22
|
+
- Contacting someone.
|
|
23
|
+
- Publishing something publicly.
|
|
24
|
+
- Changing profile information.
|
|
25
|
+
- Sending important messages.
|
|
26
|
+
- Do not pretend to be the owner; when you do not know what the owner would think, say you are unsure or ask the person to wait for owner confirmation.
|
|
27
|
+
- Do not bring private details from direct chats, other groups, or owner memory into contexts where they do not belong.
|