@clawling/clawchat-plugin-openclaw 2026.5.12-30 → 2026.5.12-32
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/commands.js +4 -6
- package/dist/src/config.js +40 -0
- package/dist/src/login.runtime.js +1 -0
- package/dist/src/outbound.js +1 -0
- package/dist/src/reply-dispatcher.js +66 -14
- 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/commands.ts +4 -6
- package/src/config.ts +51 -0
- package/src/login.runtime.ts +1 -0
- package/src/outbound.ts +1 -0
- package/src/reply-dispatcher.ts +65 -16
- 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/commands.js
CHANGED
|
@@ -64,18 +64,16 @@ function persistOutputVisibility(draft, chatId, outputVisibility) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
function formatOutputVisibilityResult(outputVisibility) {
|
|
67
|
-
const runtimeStatus = outputVisibility === "full" ? "on" : "off";
|
|
68
67
|
const detailLevel = {
|
|
69
|
-
minimal: "
|
|
70
|
-
normal: "
|
|
71
|
-
full: "
|
|
68
|
+
minimal: "final only",
|
|
69
|
+
normal: "final plus block media",
|
|
70
|
+
full: "final plus buffered reasoning, tool/progress, and block output",
|
|
72
71
|
};
|
|
73
72
|
return [
|
|
74
73
|
"**ClawChat output updated**",
|
|
75
74
|
"",
|
|
76
75
|
`- visibility: \`${outputVisibility}\``,
|
|
77
|
-
`-
|
|
78
|
-
`- detail level: \`${detailLevel[outputVisibility]}\``,
|
|
76
|
+
`- output: \`${detailLevel[outputVisibility]}\``,
|
|
79
77
|
"",
|
|
80
78
|
"Applies to new ClawChat messages.",
|
|
81
79
|
].join("\n");
|
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" },
|
|
@@ -189,6 +191,29 @@ function readGroupMode(value) {
|
|
|
189
191
|
function readGroupCommandMode(value) {
|
|
190
192
|
return value === "all" || value === "off" ? value : "owner";
|
|
191
193
|
}
|
|
194
|
+
function readOutputVisibility(value) {
|
|
195
|
+
return value === "minimal" || value === "full" ? value : "normal";
|
|
196
|
+
}
|
|
197
|
+
function readOptionalOutputVisibility(value) {
|
|
198
|
+
return value === "minimal" || value === "normal" || value === "full" ? value : undefined;
|
|
199
|
+
}
|
|
200
|
+
function readChats(value) {
|
|
201
|
+
const rawChats = value && typeof value === "object" && !Array.isArray(value)
|
|
202
|
+
? value
|
|
203
|
+
: {};
|
|
204
|
+
const chats = {};
|
|
205
|
+
for (const [chatId, rawChat] of Object.entries(rawChats)) {
|
|
206
|
+
if (!chatId)
|
|
207
|
+
continue;
|
|
208
|
+
const chat = rawChat && typeof rawChat === "object" && !Array.isArray(rawChat)
|
|
209
|
+
? rawChat
|
|
210
|
+
: {};
|
|
211
|
+
const outputVisibility = readOptionalOutputVisibility(chat.outputVisibility);
|
|
212
|
+
if (outputVisibility)
|
|
213
|
+
chats[chatId] = { outputVisibility };
|
|
214
|
+
}
|
|
215
|
+
return chats;
|
|
216
|
+
}
|
|
192
217
|
function readGroups(value) {
|
|
193
218
|
const rawGroups = value && typeof value === "object" && !Array.isArray(value)
|
|
194
219
|
? value
|
|
@@ -200,9 +225,11 @@ function readGroups(value) {
|
|
|
200
225
|
const group = rawGroup && typeof rawGroup === "object" && !Array.isArray(rawGroup)
|
|
201
226
|
? rawGroup
|
|
202
227
|
: {};
|
|
228
|
+
const outputVisibility = readOptionalOutputVisibility(group.outputVisibility);
|
|
203
229
|
groups[chatId] = {
|
|
204
230
|
groupMode: readGroupMode(group.groupMode),
|
|
205
231
|
groupCommandMode: readGroupCommandMode(group.groupCommandMode),
|
|
232
|
+
...(outputVisibility ? { outputVisibility } : {}),
|
|
206
233
|
};
|
|
207
234
|
}
|
|
208
235
|
return groups;
|
|
@@ -217,6 +244,13 @@ export function effectiveGroupCommandMode(account, chatId) {
|
|
|
217
244
|
?? account.groups["*"]?.groupCommandMode
|
|
218
245
|
?? account.groupCommandMode;
|
|
219
246
|
}
|
|
247
|
+
export function effectiveOutputVisibility(account, chatId, chatType) {
|
|
248
|
+
return account.chats?.[chatId]?.outputVisibility
|
|
249
|
+
?? (chatType === "group" ? account.groups?.[chatId]?.outputVisibility : undefined)
|
|
250
|
+
?? (chatType === "group" ? account.groups?.["*"]?.outputVisibility : undefined)
|
|
251
|
+
?? account.outputVisibility
|
|
252
|
+
?? "normal";
|
|
253
|
+
}
|
|
220
254
|
function readReconnect(raw) {
|
|
221
255
|
const s = raw && typeof raw === "object" ? raw : {};
|
|
222
256
|
return {
|
|
@@ -253,11 +287,14 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
253
287
|
const baseUrl = readOptionalString(channel.baseUrl) ||
|
|
254
288
|
readEnvString(env, CLAWCHAT_BASE_URL_ENV) ||
|
|
255
289
|
DEFAULT_BASE_URL;
|
|
290
|
+
const mediaBaseUrl = readOptionalString(channel.mediaBaseUrl) || readEnvString(env, CLAWCHAT_MEDIA_BASE_URL_ENV);
|
|
256
291
|
const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
|
|
257
292
|
const agentId = readOptionalString(channel.agentId) || readEnvString(env, CLAWCHAT_AGENT_ID_ENV);
|
|
258
293
|
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
259
294
|
const ownerUserId = readOptionalString(channel.ownerUserId) || readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV);
|
|
260
295
|
const enabled = typeof channel.enabled === "boolean" ? channel.enabled : true;
|
|
296
|
+
const outputVisibility = readOutputVisibility(channel.outputVisibility);
|
|
297
|
+
const chats = readChats(channel.chats);
|
|
261
298
|
const groupMode = readGroupMode(channel.groupMode);
|
|
262
299
|
const groupCommandMode = readGroupCommandMode(channel.groupCommandMode);
|
|
263
300
|
const groups = readGroups(channel.groups);
|
|
@@ -276,10 +313,13 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
276
313
|
}),
|
|
277
314
|
websocketUrl,
|
|
278
315
|
baseUrl,
|
|
316
|
+
mediaBaseUrl,
|
|
279
317
|
token,
|
|
280
318
|
agentId,
|
|
281
319
|
userId,
|
|
282
320
|
ownerUserId,
|
|
321
|
+
outputVisibility,
|
|
322
|
+
chats,
|
|
283
323
|
groupMode,
|
|
284
324
|
groupCommandMode,
|
|
285
325
|
groups,
|
|
@@ -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
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { interactiveReplyToPresentation, renderMessagePresentationFallbackText, } from "openclaw/plugin-sdk/interactive-runtime";
|
|
2
2
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
3
3
|
import { createOpenclawClawlingApiClient } from "./api-client.js";
|
|
4
|
+
import { effectiveOutputVisibility, } from "./config.js";
|
|
4
5
|
import { uploadOutboundMedia } from "./media-runtime.js";
|
|
5
6
|
import { sendOpenclawClawlingText, } from "./outbound.js";
|
|
6
7
|
import { isClawChatNoopResponseText } from "./profile-prompt.js";
|
|
@@ -106,15 +107,14 @@ function resolvePayloadText(payload) {
|
|
|
106
107
|
/**
|
|
107
108
|
* Reply dispatcher for clawchat-plugin-openclaw.
|
|
108
109
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* blocks, the dispatcher buffers or ignores them and only emits materialized
|
|
113
|
-
* `message.send` / `message.reply` frames for the final reply.
|
|
110
|
+
* ClawChat emits only materialized `message.send` / `message.reply` frames for
|
|
111
|
+
* the final reply. Non-final OpenClaw deliveries are ignored or buffered
|
|
112
|
+
* according to outputVisibility and are never sent as separate ClawChat frames.
|
|
114
113
|
*/
|
|
115
114
|
export function createOpenclawClawlingReplyDispatcher(options) {
|
|
116
115
|
const { cfg, runtime, account, client, target, replyCtx, inboundMessageId, store, log, } = options;
|
|
117
116
|
const isGroupTarget = target.chatType === "group";
|
|
117
|
+
const outputVisibility = effectiveOutputVisibility(account, target.chatId, target.chatType);
|
|
118
118
|
const ownerDirectTarget = () => {
|
|
119
119
|
const ownerUserId = account.ownerUserId?.trim();
|
|
120
120
|
return ownerUserId ? { chatId: ownerUserId, chatType: "direct" } : null;
|
|
@@ -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
|
});
|
|
@@ -141,6 +142,8 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
141
142
|
}
|
|
142
143
|
// ----- Reply state ------------------------------------------------------
|
|
143
144
|
let reasoningText = "";
|
|
145
|
+
let bufferedOutputText = "";
|
|
146
|
+
const bufferedOutputUrls = [];
|
|
144
147
|
let runDone = false;
|
|
145
148
|
let typingActive = false;
|
|
146
149
|
let terminalReplySuppressed = false;
|
|
@@ -228,6 +231,37 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
228
231
|
recordOutbound("thinking", messageId, thinkingText);
|
|
229
232
|
reasoningText = "";
|
|
230
233
|
};
|
|
234
|
+
const resetBufferedOutput = () => {
|
|
235
|
+
bufferedOutputText = "";
|
|
236
|
+
bufferedOutputUrls.length = 0;
|
|
237
|
+
};
|
|
238
|
+
const appendBufferedText = (value) => {
|
|
239
|
+
const trimmed = value.trim();
|
|
240
|
+
if (!trimmed)
|
|
241
|
+
return;
|
|
242
|
+
bufferedOutputText = bufferedOutputText ? `${bufferedOutputText}\n${trimmed}` : trimmed;
|
|
243
|
+
};
|
|
244
|
+
const appendBufferedUrls = (urls) => {
|
|
245
|
+
for (const url of urls) {
|
|
246
|
+
if (url && !bufferedOutputUrls.includes(url))
|
|
247
|
+
bufferedOutputUrls.push(url);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const mergeFinalText = (text) => {
|
|
251
|
+
if (outputVisibility !== "full")
|
|
252
|
+
return text;
|
|
253
|
+
return [bufferedOutputText.trim(), text.trim()].filter(Boolean).join("\n");
|
|
254
|
+
};
|
|
255
|
+
const mergeFinalUrls = (urls) => {
|
|
256
|
+
if (outputVisibility === "minimal")
|
|
257
|
+
return urls;
|
|
258
|
+
const merged = bufferedOutputUrls.slice();
|
|
259
|
+
for (const url of urls) {
|
|
260
|
+
if (url && !merged.includes(url))
|
|
261
|
+
merged.push(url);
|
|
262
|
+
}
|
|
263
|
+
return merged;
|
|
264
|
+
};
|
|
231
265
|
const mintStaticMessageId = () => `${account.userId}-msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
232
266
|
const emitTyping = (isTyping) => {
|
|
233
267
|
if (!isTyping && !typingActive)
|
|
@@ -341,6 +375,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
341
375
|
onReplyStart: async () => {
|
|
342
376
|
emitTyping(true);
|
|
343
377
|
reasoningText = "";
|
|
378
|
+
resetBufferedOutput();
|
|
344
379
|
runDone = false;
|
|
345
380
|
},
|
|
346
381
|
deliver: async (payload, info) => {
|
|
@@ -361,13 +396,30 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
361
396
|
return;
|
|
362
397
|
}
|
|
363
398
|
if (payload.isReasoning) {
|
|
364
|
-
if (isGroupTarget ||
|
|
399
|
+
if (isGroupTarget || outputVisibility !== "full")
|
|
365
400
|
return;
|
|
366
|
-
|
|
401
|
+
appendBufferedText(text);
|
|
402
|
+
const trimmed = text.trim();
|
|
403
|
+
if (trimmed)
|
|
404
|
+
reasoningText = reasoningText ? `${reasoningText}\n${trimmed}` : trimmed;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (info?.kind === "tool") {
|
|
408
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
409
|
+
appendBufferedText(text);
|
|
410
|
+
appendBufferedUrls(urls);
|
|
411
|
+
}
|
|
367
412
|
return;
|
|
368
413
|
}
|
|
369
|
-
if (info?.kind === "
|
|
414
|
+
if (info?.kind === "block") {
|
|
415
|
+
if (!isGroupTarget && outputVisibility === "normal")
|
|
416
|
+
appendBufferedUrls(urls);
|
|
417
|
+
if (!isGroupTarget && outputVisibility === "full") {
|
|
418
|
+
appendBufferedText(text);
|
|
419
|
+
appendBufferedUrls(urls);
|
|
420
|
+
}
|
|
370
421
|
return;
|
|
422
|
+
}
|
|
371
423
|
if (info?.kind === "final") {
|
|
372
424
|
if (isClawChatNoopResponseText(text) && !richFragment && urls.length === 0) {
|
|
373
425
|
log?.info?.(`[${account.accountId}] clawchat-plugin-openclaw final suppressed: no-reply token`);
|
|
@@ -390,15 +442,15 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
390
442
|
});
|
|
391
443
|
return;
|
|
392
444
|
}
|
|
393
|
-
const
|
|
394
|
-
const
|
|
445
|
+
const finalText = richFragment && account.richInteractions ? mergeFinalText("") : mergeFinalText(text);
|
|
446
|
+
const finalUrls = mergeFinalUrls(urls);
|
|
447
|
+
const mediaFragments = await uploadMediaUrls(finalUrls);
|
|
448
|
+
const result = await sendStatic(finalText, mediaFragments, richFragment && account.richInteractions ? [richFragment] : [], { recordMessage: true });
|
|
395
449
|
if (result?.messageId)
|
|
396
450
|
recordThinkingIfLinked(result.messageId);
|
|
397
451
|
return;
|
|
398
452
|
}
|
|
399
|
-
//
|
|
400
|
-
// the model is producing output. ClawChat gets only the final materialized
|
|
401
|
-
// reply.
|
|
453
|
+
// Unknown delivery kind: keep ClawChat output tied to OpenClaw final.
|
|
402
454
|
},
|
|
403
455
|
onError: (error, info) => {
|
|
404
456
|
const errorText = normalizeReplyErrorText(error);
|
|
@@ -423,7 +475,7 @@ export function createOpenclawClawlingReplyDispatcher(options) {
|
|
|
423
475
|
replyOptions: {
|
|
424
476
|
...base.replyOptions,
|
|
425
477
|
sourceReplyDeliveryMode: "automatic",
|
|
426
|
-
disableBlockStreaming:
|
|
478
|
+
disableBlockStreaming: outputVisibility !== "full",
|
|
427
479
|
},
|
|
428
480
|
markDispatchIdle: base.markDispatchIdle,
|
|
429
481
|
};
|
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",
|