@clawling/clawchat-plugin-openclaw 2026.5.12-28 → 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/LICENSE +21 -0
- package/README.md +4 -50
- package/dist/src/api-client.js +37 -4
- package/dist/src/channel.js +4 -4
- package/dist/src/channel.setup.js +2 -2
- package/dist/src/commands.js +102 -0
- package/dist/src/config.js +28 -1
- package/dist/src/group-message-coalescer.js +21 -37
- package/dist/src/login.runtime.js +1 -0
- package/dist/src/outbound.js +1 -0
- package/dist/src/profile-prompt.js +70 -31
- package/dist/src/reply-dispatcher.js +9 -0
- package/dist/src/runtime.js +159 -5
- package/dist/src/storage.js +19 -0
- package/dist/src/tools-schema.js +34 -2
- package/dist/src/tools.js +130 -15
- package/openclaw.plugin.json +38 -5
- package/package.json +8 -3
- package/prompts/default-group-bio.md +14 -14
- package/prompts/default-owner-behavior.md +22 -22
- package/prompts/platform.md +2 -2
- package/skills/clawchat/SKILL.md +5 -1
- package/src/api-client.ts +61 -5
- package/src/api-types.ts +17 -0
- package/src/channel.setup.ts +2 -1
- package/src/channel.ts +4 -3
- package/src/commands.ts +113 -0
- package/src/config.ts +48 -1
- package/src/group-message-coalescer.ts +35 -36
- package/src/inbound.ts +11 -0
- package/src/login.runtime.ts +1 -0
- package/src/outbound.ts +1 -0
- package/src/profile-prompt.ts +83 -31
- package/src/reply-dispatcher.ts +11 -0
- package/src/runtime.ts +197 -6
- package/src/storage.ts +38 -0
- package/src/tools-schema.ts +49 -2
- package/src/tools.ts +170 -14
- 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 -262
- package/src/clawchat-memory.test.ts +0 -480
- package/src/clawchat-metadata.test.ts +0 -477
- package/src/client.test.ts +0 -169
- package/src/commands.test.ts +0 -39
- package/src/config.test.ts +0 -344
- package/src/group-message-coalescer.test.ts +0 -237
- 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 -352
- 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 -38
- package/src/plugin-prompts.test.ts +0 -94
- package/src/profile-prompt.test.ts +0 -274
- 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 -1324
- package/src/runtime.test.ts +0 -4719
- package/src/scripts.test.ts +0 -85
- package/src/storage.test.ts +0 -560
- 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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CLAWLING PTE. LTD.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,12 +5,11 @@ OpenClaw channel plugin that connects an agent to ClawChat over ClawChat Protoco
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- Plugin-owned WebSocket transport with auto-reconnect (exponential backoff + jitter), heartbeat, and ack tracking
|
|
8
|
-
- Invite-code onboarding — no raw credentials required
|
|
9
8
|
- Inbound `message.send` / `message.reply` with reply context
|
|
10
9
|
- Outbound text replies in `static` or `stream` mode, with a consolidated final `message.reply`
|
|
11
10
|
- Typing indicators and filtered forwarding for thinking / tool-call content
|
|
12
11
|
- Media fragments (image / file / audio / video) in either direction
|
|
13
|
-
- Invite-code onboarding via `/clawchat-activate` or supported `openclaw channels add`, plus always-registered `clawchat_*` account/media tools
|
|
12
|
+
- Invite-code onboarding (no raw credentials) via `/clawchat-activate` or supported `openclaw channels add`, plus always-registered `clawchat_*` account/media tools
|
|
14
13
|
|
|
15
14
|
## Install
|
|
16
15
|
|
|
@@ -21,13 +20,10 @@ npm i @clawling/clawchat-plugin-openclaw
|
|
|
21
20
|
|
|
22
21
|
Requires `openclaw >= 2026.5.4` as a peer host.
|
|
23
22
|
|
|
24
|
-
For the OpenClaw plugin install/update flow, use the R2-hosted tarball install
|
|
25
|
-
command documented in [`INSTALL.md`](./INSTALL.md).
|
|
26
|
-
|
|
27
23
|
Example LLM prompt:
|
|
28
24
|
|
|
29
25
|
```text
|
|
30
|
-
|
|
26
|
+
Install and activate the ClawChat plugin from npm package @clawling/clawchat-plugin-openclaw. The invite code is XXXXXX.
|
|
31
27
|
```
|
|
32
28
|
|
|
33
29
|
## Current activation paths
|
|
@@ -166,7 +162,8 @@ npm run typecheck
|
|
|
166
162
|
|
|
167
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/`.
|
|
168
164
|
|
|
169
|
-
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.
|
|
170
167
|
|
|
171
168
|
For OpenClaw host SDK/source lookup while developing this plugin, optionally
|
|
172
169
|
clone OpenClaw into `tmp/openclaw`:
|
|
@@ -179,49 +176,6 @@ npm run dev:openclaw-source
|
|
|
179
176
|
This checkout is local-only. It is ignored by git and is not required to run the
|
|
180
177
|
plugin tests or publish the package.
|
|
181
178
|
|
|
182
|
-
## R2 package scripts
|
|
183
|
-
|
|
184
|
-
Create and upload the OpenClaw plugin tarball to the R2 `openclaw/` prefix:
|
|
185
|
-
|
|
186
|
-
```bash
|
|
187
|
-
./scripts/package_openclaw_plugin.sh
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
The script runs `npm pack`, removes `devDependencies` from the generated `.tgz`
|
|
191
|
-
metadata so OpenClaw installs only runtime dependencies, uploads the `.tgz` to
|
|
192
|
-
the configured R2 bucket, updates the `latest` R2 alias, uploads `INSTALL.md` as
|
|
193
|
-
`openclaw/install.md`, and prints the public URLs. R2 credentials are read from
|
|
194
|
-
`scripts/.env.r2`, which is ignored by git. Copy `scripts/.env.r2.example` to
|
|
195
|
-
`scripts/.env.r2` and fill in the credentials. Use `--no-upload` to build the
|
|
196
|
-
tarball without uploading it.
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
AWS_ACCESS_KEY_ID=...
|
|
200
|
-
AWS_SECRET_ACCESS_KEY=...
|
|
201
|
-
AWS_DEFAULT_REGION=auto
|
|
202
|
-
R2_ENDPOINT=https://...
|
|
203
|
-
R2_BUCKET=...
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Install the R2-hosted latest tarball on a device or container with OpenClaw
|
|
207
|
-
available:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
./scripts/install_openclaw.sh
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
To install a specific uploaded version, pass the version string:
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
./scripts/install_openclaw.sh 2026.5.16-1
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
To install a specific uploaded tarball URL, pass its URL explicitly:
|
|
220
|
-
|
|
221
|
-
```bash
|
|
222
|
-
./scripts/install_openclaw.sh https://plugin.clawling.chat/openclaw/newbase-clawchat-clawchat-plugin-openclaw-2026.5.16-1.tgz
|
|
223
|
-
```
|
|
224
|
-
|
|
225
179
|
## License
|
|
226
180
|
|
|
227
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/channel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
|
3
|
-
import { CHANNEL_ID, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
3
|
+
import { CHANNEL_ID, canStartOpenclawClawlingAccount, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
4
4
|
import { openclawClawlingOutbound } from "./outbound.js";
|
|
5
5
|
import { getOpenclawClawlingRuntime, startOpenclawClawlingGateway } from "./runtime.js";
|
|
6
6
|
import { openclawClawlingSetupPlugin } from "./channel.setup.js";
|
|
@@ -27,9 +27,9 @@ export const openclawClawlingPlugin = createChatChannelPlugin({
|
|
|
27
27
|
startAccount: async (ctx) => {
|
|
28
28
|
const account = ctx.account ?? resolveOpenclawClawlingAccount(ctx.cfg);
|
|
29
29
|
ctx.log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw lifecycle START_ACCOUNT_CALLED configured=${account.configured} enabled=${account.enabled} hasToken=${Boolean(account.token)} hasUserId=${Boolean(account.userId)} websocketUrl=${account.websocketUrl || "(empty)"}`);
|
|
30
|
-
if (!account
|
|
31
|
-
ctx.log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw lifecycle startAccount refused: websocketUrl
|
|
32
|
-
throw new Error("Clawling Chat websocketUrl
|
|
30
|
+
if (!canStartOpenclawClawlingAccount(account)) {
|
|
31
|
+
ctx.log?.error?.(`[${account.accountId}] clawchat-plugin-openclaw lifecycle startAccount refused: websocketUrl is required`);
|
|
32
|
+
throw new Error("Clawling Chat websocketUrl is required");
|
|
33
33
|
}
|
|
34
34
|
try {
|
|
35
35
|
await startOpenclawClawlingGateway({
|
|
@@ -2,7 +2,7 @@ import { createTopLevelChannelConfigAdapter } from "openclaw/plugin-sdk/channel-
|
|
|
2
2
|
import { mutateConfigFile } from "openclaw/plugin-sdk/config-mutation";
|
|
3
3
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
|
4
4
|
import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, } from "openclaw/plugin-sdk/status-helpers";
|
|
5
|
-
import { CHANNEL_ID, listOpenclawClawlingAccountIds, openclawClawlingConfigSchema, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
5
|
+
import { CHANNEL_ID, canStartOpenclawClawlingAccount, listOpenclawClawlingAccountIds, openclawClawlingConfigSchema, resolveOpenclawClawlingAccount, } from "./config.js";
|
|
6
6
|
const configAdapter = createTopLevelChannelConfigAdapter({
|
|
7
7
|
sectionKey: CHANNEL_ID,
|
|
8
8
|
resolveAccount: (cfg) => resolveOpenclawClawlingAccount(cfg),
|
|
@@ -89,7 +89,7 @@ export const openclawClawlingSetupPlugin = {
|
|
|
89
89
|
},
|
|
90
90
|
config: {
|
|
91
91
|
...configAdapter,
|
|
92
|
-
isConfigured: (account) => account
|
|
92
|
+
isConfigured: (account) => canStartOpenclawClawlingAccount(account),
|
|
93
93
|
describeAccount: (account) => ({
|
|
94
94
|
accountId: account.accountId,
|
|
95
95
|
name: account.name,
|
package/dist/src/commands.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CHANNEL_ID } from "./config.js";
|
|
1
2
|
function extractInviteCode(value) {
|
|
2
3
|
const raw = typeof value === "string" ? value.trim() : "";
|
|
3
4
|
return raw.match(/\b[A-Z0-9]{6}\b/u)?.[0] ?? "";
|
|
@@ -5,6 +6,80 @@ function extractInviteCode(value) {
|
|
|
5
6
|
function errorMessage(err) {
|
|
6
7
|
return err instanceof Error ? err.message : String(err);
|
|
7
8
|
}
|
|
9
|
+
function readRecord(value) {
|
|
10
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
11
|
+
? value
|
|
12
|
+
: {};
|
|
13
|
+
}
|
|
14
|
+
function extractOutputVisibility(value) {
|
|
15
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
16
|
+
const token = raw.split(/\s+/, 1)[0] ?? "";
|
|
17
|
+
return token === "minimal" || token === "normal" || token === "full" ? token : null;
|
|
18
|
+
}
|
|
19
|
+
function stripChannelPrefix(value) {
|
|
20
|
+
const raw = value.trim();
|
|
21
|
+
const prefix = `${CHANNEL_ID}:`;
|
|
22
|
+
return raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
23
|
+
}
|
|
24
|
+
function extractChatIdFromRoute(value) {
|
|
25
|
+
if (typeof value !== "string")
|
|
26
|
+
return "";
|
|
27
|
+
const stripped = stripChannelPrefix(value);
|
|
28
|
+
if (!stripped)
|
|
29
|
+
return "";
|
|
30
|
+
if (stripped.startsWith("group:"))
|
|
31
|
+
return stripped.slice("group:".length).trim();
|
|
32
|
+
if (stripped.startsWith("direct:"))
|
|
33
|
+
return stripped.slice("direct:".length).trim();
|
|
34
|
+
return stripped.trim();
|
|
35
|
+
}
|
|
36
|
+
function resolveCommandChatId(ctx) {
|
|
37
|
+
return extractChatIdFromRoute(ctx.from)
|
|
38
|
+
|| extractChatIdFromRoute(ctx.to)
|
|
39
|
+
|| (typeof ctx.threadParentId === "string" ? ctx.threadParentId.trim() : "")
|
|
40
|
+
|| (typeof ctx.messageThreadId === "string" || typeof ctx.messageThreadId === "number"
|
|
41
|
+
? String(ctx.messageThreadId).trim()
|
|
42
|
+
: "");
|
|
43
|
+
}
|
|
44
|
+
function persistOutputVisibility(draft, chatId, outputVisibility) {
|
|
45
|
+
const channels = readRecord(draft.channels);
|
|
46
|
+
const channel = readRecord(channels[CHANNEL_ID]);
|
|
47
|
+
const chats = readRecord(channel.chats);
|
|
48
|
+
const chat = readRecord(chats[chatId]);
|
|
49
|
+
Object.assign(draft, {
|
|
50
|
+
...draft,
|
|
51
|
+
channels: {
|
|
52
|
+
...channels,
|
|
53
|
+
[CHANNEL_ID]: {
|
|
54
|
+
...channel,
|
|
55
|
+
chats: {
|
|
56
|
+
...chats,
|
|
57
|
+
[chatId]: {
|
|
58
|
+
...chat,
|
|
59
|
+
outputVisibility,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function formatOutputVisibilityResult(outputVisibility) {
|
|
67
|
+
const runtimeStatus = outputVisibility === "full" ? "on" : "off";
|
|
68
|
+
const detailLevel = {
|
|
69
|
+
minimal: "quiet",
|
|
70
|
+
normal: "normal",
|
|
71
|
+
full: "verbose",
|
|
72
|
+
};
|
|
73
|
+
return [
|
|
74
|
+
"**ClawChat output updated**",
|
|
75
|
+
"",
|
|
76
|
+
`- visibility: \`${outputVisibility}\``,
|
|
77
|
+
`- runtime status: \`${runtimeStatus}\``,
|
|
78
|
+
`- detail level: \`${detailLevel[outputVisibility]}\``,
|
|
79
|
+
"",
|
|
80
|
+
"Applies to new ClawChat messages.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
8
83
|
export function registerOpenclawClawlingCommands(api) {
|
|
9
84
|
api.registerCommand({
|
|
10
85
|
name: "clawchat-activate",
|
|
@@ -32,4 +107,31 @@ export function registerOpenclawClawlingCommands(api) {
|
|
|
32
107
|
}
|
|
33
108
|
},
|
|
34
109
|
});
|
|
110
|
+
api.registerCommand({
|
|
111
|
+
name: "clawchat-output",
|
|
112
|
+
description: "Set ClawChat output visibility for the current conversation.",
|
|
113
|
+
acceptsArgs: true,
|
|
114
|
+
requireAuth: true,
|
|
115
|
+
async handler(ctx) {
|
|
116
|
+
const outputVisibility = extractOutputVisibility(ctx.args ?? ctx.commandBody);
|
|
117
|
+
if (!outputVisibility) {
|
|
118
|
+
return { text: "Usage: /clawchat-output minimal|normal|full" };
|
|
119
|
+
}
|
|
120
|
+
const chatId = resolveCommandChatId(ctx);
|
|
121
|
+
if (!chatId) {
|
|
122
|
+
return { text: "Unable to determine the current ClawChat conversation for /clawchat-output." };
|
|
123
|
+
}
|
|
124
|
+
const mutateConfigFile = api.runtime.config.mutateConfigFile;
|
|
125
|
+
if (!mutateConfigFile) {
|
|
126
|
+
return { text: "OpenClaw runtime config mutation is unavailable for /clawchat-output." };
|
|
127
|
+
}
|
|
128
|
+
await mutateConfigFile({
|
|
129
|
+
afterWrite: { mode: "auto" },
|
|
130
|
+
mutate(draft) {
|
|
131
|
+
persistOutputVisibility(draft, chatId, outputVisibility);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return { text: formatOutputVisibilityResult(outputVisibility) };
|
|
135
|
+
},
|
|
136
|
+
});
|
|
35
137
|
}
|
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,11 +47,23 @@ 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" },
|
|
52
54
|
userId: { type: "string" },
|
|
53
55
|
ownerUserId: { type: "string" },
|
|
56
|
+
outputVisibility: { type: "string", enum: ["minimal", "normal", "full"] },
|
|
57
|
+
chats: {
|
|
58
|
+
type: "object",
|
|
59
|
+
additionalProperties: {
|
|
60
|
+
type: "object",
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
properties: {
|
|
63
|
+
outputVisibility: { type: "string", enum: ["minimal", "normal", "full"] },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
54
67
|
groupMode: { type: "string", enum: ["mention", "all"] },
|
|
55
68
|
groupCommandMode: { type: "string", enum: ["owner", "all", "off"] },
|
|
56
69
|
groups: {
|
|
@@ -61,6 +74,7 @@ export const openclawClawlingConfigSchema = {
|
|
|
61
74
|
properties: {
|
|
62
75
|
groupMode: { type: "string", enum: ["mention", "all"] },
|
|
63
76
|
groupCommandMode: { type: "string", enum: ["owner", "all", "off"] },
|
|
77
|
+
outputVisibility: { type: "string", enum: ["minimal", "normal", "full"] },
|
|
64
78
|
},
|
|
65
79
|
},
|
|
66
80
|
},
|
|
@@ -154,6 +168,12 @@ export function mergeOpenclawClawchatRuntimePluginActivation(cfg) {
|
|
|
154
168
|
plugins: nextPlugins,
|
|
155
169
|
};
|
|
156
170
|
}
|
|
171
|
+
export function hasOpenclawClawlingConnectCredentials(account) {
|
|
172
|
+
return Boolean(account.websocketUrl && account.token && account.userId && account.ownerUserId);
|
|
173
|
+
}
|
|
174
|
+
export function canStartOpenclawClawlingAccount(account) {
|
|
175
|
+
return Boolean(account.enabled && account.websocketUrl);
|
|
176
|
+
}
|
|
157
177
|
function readChannelSection(cfg) {
|
|
158
178
|
const channels = (cfg.channels ?? {});
|
|
159
179
|
const channel = channels[CHANNEL_ID];
|
|
@@ -235,6 +255,7 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
235
255
|
const baseUrl = readOptionalString(channel.baseUrl) ||
|
|
236
256
|
readEnvString(env, CLAWCHAT_BASE_URL_ENV) ||
|
|
237
257
|
DEFAULT_BASE_URL;
|
|
258
|
+
const mediaBaseUrl = readOptionalString(channel.mediaBaseUrl) || readEnvString(env, CLAWCHAT_MEDIA_BASE_URL_ENV);
|
|
238
259
|
const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
|
|
239
260
|
const agentId = readOptionalString(channel.agentId) || readEnvString(env, CLAWCHAT_AGENT_ID_ENV);
|
|
240
261
|
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
@@ -250,9 +271,15 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
250
271
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
251
272
|
name: CHANNEL_ID,
|
|
252
273
|
enabled,
|
|
253
|
-
configured:
|
|
274
|
+
configured: hasOpenclawClawlingConnectCredentials({
|
|
275
|
+
websocketUrl,
|
|
276
|
+
token,
|
|
277
|
+
userId,
|
|
278
|
+
ownerUserId,
|
|
279
|
+
}),
|
|
254
280
|
websocketUrl,
|
|
255
281
|
baseUrl,
|
|
282
|
+
mediaBaseUrl,
|
|
256
283
|
token,
|
|
257
284
|
agentId,
|
|
258
285
|
userId,
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
function formatTurnTime(timestamp) {
|
|
2
|
-
if (!Number.isFinite(timestamp))
|
|
3
|
-
return "unknown-time";
|
|
4
|
-
const time = new Date(timestamp);
|
|
5
|
-
if (Number.isNaN(time.getTime()))
|
|
6
|
-
return "unknown-time";
|
|
7
|
-
return time.toISOString();
|
|
8
|
-
}
|
|
9
1
|
function formatSenderRelation(turn) {
|
|
10
2
|
return turn.senderRelation || "peer_user";
|
|
11
3
|
}
|
|
@@ -21,40 +13,31 @@ function formatMessageBody(rawBody) {
|
|
|
21
13
|
function formatField(value) {
|
|
22
14
|
return value.replace(/\\/g, "\\\\").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
23
15
|
}
|
|
24
|
-
function formatMentionedUsers(turn) {
|
|
25
|
-
const mentionedUsers = turn.mentionedUsers && turn.mentionedUsers.length > 0
|
|
26
|
-
? turn.mentionedUsers
|
|
27
|
-
: turn.mentionedUserIds.map((id) => ({ id }));
|
|
28
|
-
if (mentionedUsers.length === 0)
|
|
29
|
-
return "-";
|
|
30
|
-
return mentionedUsers.map((mention) => {
|
|
31
|
-
const id = formatField(mention.id);
|
|
32
|
-
const display = mention.display?.trim();
|
|
33
|
-
return display ? `${id}(${formatField(display)})` : id;
|
|
34
|
-
}).join(",");
|
|
35
|
-
}
|
|
36
16
|
export function formatCoalescedGroupBody(turns, timing = { idleSeconds: 10, maxWaitSeconds: 30 }) {
|
|
37
|
-
|
|
17
|
+
void timing;
|
|
38
18
|
return [
|
|
39
|
-
|
|
40
|
-
turns.map((turn) => {
|
|
19
|
+
"ClawChat group messages:",
|
|
20
|
+
turns.map((turn, index) => {
|
|
41
21
|
const senderName = turn.senderNickName || turn.senderId;
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`sender_name: ${formatField(senderName)}`,
|
|
47
|
-
`sender_profile_type: ${formatField(formatSenderProfileType(turn))}`,
|
|
48
|
-
`sender_is_agent_owner: ${senderIsAgentOwner ? "true" : "false"}`,
|
|
49
|
-
`sender_is_group_owner: ${turn.senderIsGroupOwner ? "true" : "false"}`,
|
|
50
|
-
`mentions_current_agent: ${turn.wasMentioned ? "true" : "false"}`,
|
|
51
|
-
`mentioned_users: ${formatMentionedUsers(turn)}`,
|
|
52
|
-
"text:",
|
|
53
|
-
formatMessageBody(turn.rawBody),
|
|
54
|
-
].join("\n");
|
|
55
|
-
}).join("\n\n"),
|
|
22
|
+
const label = `[message ${index + 1}] ${formatField(senderName)}:`;
|
|
23
|
+
const body = formatMessageBody(turn.rawBody);
|
|
24
|
+
return body.includes("\n") ? `${label}\n${body}` : `${label} ${body}`;
|
|
25
|
+
}).join("\n"),
|
|
56
26
|
].join("\n");
|
|
57
27
|
}
|
|
28
|
+
function groupMessageForPrompt(turn) {
|
|
29
|
+
return {
|
|
30
|
+
senderId: turn.senderId,
|
|
31
|
+
senderName: turn.senderNickName || turn.senderId,
|
|
32
|
+
senderRelation: turn.senderRelation,
|
|
33
|
+
senderProfileType: formatSenderProfileType(turn),
|
|
34
|
+
senderIsOwner: turn.senderIsOwner ?? formatSenderRelation(turn) === "owner",
|
|
35
|
+
senderIsGroupOwner: turn.senderIsGroupOwner,
|
|
36
|
+
wasMentioned: turn.wasMentioned,
|
|
37
|
+
mentionedUserIds: turn.mentionedUserIds,
|
|
38
|
+
mentionedUsers: turn.mentionedUsers,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
58
41
|
export function mergeGroupTurns(turns, timing = { idleSeconds: 10, maxWaitSeconds: 30 }) {
|
|
59
42
|
if (turns.length === 0)
|
|
60
43
|
throw new Error("cannot merge empty group turn batch");
|
|
@@ -62,6 +45,7 @@ export function mergeGroupTurns(turns, timing = { idleSeconds: 10, maxWaitSecond
|
|
|
62
45
|
return {
|
|
63
46
|
...latest,
|
|
64
47
|
rawBody: formatCoalescedGroupBody(turns, timing),
|
|
48
|
+
groupMessages: turns.map(groupMessageForPrompt),
|
|
65
49
|
mediaItems: turns.flatMap((turn) => turn.mediaItems),
|
|
66
50
|
wasMentioned: turns.some((turn) => turn.wasMentioned),
|
|
67
51
|
mentionedUserIds: Array.from(new Set(turns.flatMap((turn) => turn.mentionedUserIds))),
|
|
@@ -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
|
});
|