@newbase-clawchat/openclaw-clawchat 2026.5.12-2 → 2026.5.12-21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -17
- package/dist/index.js +3 -1
- package/dist/src/api-client.js +71 -12
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/channel.js +5 -5
- package/dist/src/channel.setup.js +4 -17
- package/dist/src/clawchat-memory.js +290 -0
- package/dist/src/clawchat-metadata.js +235 -0
- package/dist/src/client.js +31 -93
- package/dist/src/commands.js +3 -3
- package/dist/src/config.js +58 -3
- package/dist/src/group-message-coalescer.js +107 -0
- package/dist/src/inbound.js +24 -28
- package/dist/src/login.runtime.js +82 -19
- package/dist/src/media-runtime.js +2 -3
- package/dist/src/message-mapper.js +1 -1
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +281 -56
- package/dist/src/plugin-prompts.js +76 -0
- package/dist/src/profile-prompt.js +150 -0
- package/dist/src/profile-sync.js +169 -0
- package/dist/src/prompt-injection.js +25 -0
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +2 -2
- package/dist/src/reply-dispatcher.js +143 -40
- package/dist/src/runtime.js +813 -109
- package/dist/src/storage.js +636 -0
- package/dist/src/tools-schema.js +70 -10
- package/dist/src/tools.js +600 -112
- package/dist/src/ws-alignment.js +8 -0
- package/dist/src/ws-client.js +588 -0
- package/index.ts +6 -1
- package/openclaw.plugin.json +44 -4
- package/package.json +4 -3
- package/prompts/platform.md +7 -0
- package/skills/clawchat/SKILL.md +90 -0
- package/src/api-client.test.ts +360 -15
- package/src/api-client.ts +127 -25
- package/src/api-types.test-d.ts +12 -0
- package/src/api-types.ts +71 -4
- package/src/buffered-stream.test.ts +1 -1
- package/src/buffered-stream.ts +1 -1
- package/src/channel.outbound.test.ts +270 -60
- package/src/channel.setup.ts +9 -18
- package/src/channel.test.ts +33 -25
- package/src/channel.ts +5 -7
- package/src/clawchat-memory.test.ts +372 -0
- package/src/clawchat-memory.ts +363 -0
- package/src/clawchat-metadata.test.ts +350 -0
- package/src/clawchat-metadata.ts +352 -0
- package/src/client.test.ts +57 -48
- package/src/client.ts +37 -129
- package/src/commands.test.ts +2 -2
- package/src/commands.ts +3 -3
- package/src/config.test.ts +169 -4
- package/src/config.ts +86 -6
- package/src/group-message-coalescer.test.ts +223 -0
- package/src/group-message-coalescer.ts +154 -0
- package/src/inbound.test.ts +106 -19
- package/src/inbound.ts +31 -35
- package/src/login.runtime.test.ts +294 -11
- package/src/login.runtime.ts +90 -21
- package/src/manifest.test.ts +86 -14
- package/src/media-runtime.test.ts +31 -2
- package/src/media-runtime.ts +7 -10
- package/src/message-mapper.test.ts +2 -2
- package/src/message-mapper.ts +2 -2
- package/src/mock-transport.test.ts +35 -0
- package/src/mock-transport.ts +38 -0
- package/src/outbound.test.ts +811 -95
- package/src/outbound.ts +332 -65
- package/src/plugin-entry.test.ts +3 -1
- package/src/plugin-prompts.test.ts +78 -0
- package/src/plugin-prompts.ts +92 -0
- package/src/profile-prompt.test.ts +435 -0
- package/src/profile-prompt.ts +208 -0
- package/src/profile-sync.test.ts +611 -0
- package/src/profile-sync.ts +268 -0
- package/src/prompt-injection.test.ts +39 -0
- package/src/prompt-injection.ts +45 -0
- package/src/protocol-types.test.ts +69 -0
- package/src/protocol-types.ts +296 -0
- package/src/protocol-types.typecheck.ts +89 -0
- package/src/protocol.ts +2 -2
- package/src/reply-dispatcher.test.ts +720 -135
- package/src/reply-dispatcher.ts +174 -42
- package/src/runtime.test.ts +3884 -337
- package/src/runtime.ts +956 -128
- package/src/storage.test.ts +692 -0
- package/src/storage.ts +989 -0
- package/src/streaming.test.ts +1 -1
- package/src/streaming.ts +1 -1
- package/src/tools-schema.ts +115 -13
- package/src/tools.test.ts +501 -10
- package/src/tools.ts +739 -133
- package/src/ws-alignment.ts +9 -0
- package/src/ws-client.test.ts +1218 -0
- package/src/ws-client.ts +662 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { readClawChatMemoryFile, writeClawChatMetadata, } from "./clawchat-memory.js";
|
|
2
|
+
function asRecord(value) {
|
|
3
|
+
return value && typeof value === "object" ? value : null;
|
|
4
|
+
}
|
|
5
|
+
function hasOwn(record, key) {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
7
|
+
}
|
|
8
|
+
function stringField(record, key) {
|
|
9
|
+
if (!hasOwn(record, key))
|
|
10
|
+
return undefined;
|
|
11
|
+
const value = record[key];
|
|
12
|
+
return typeof value === "string" ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function addStringField(metadata, outputKey, record, inputKey = outputKey) {
|
|
15
|
+
const value = stringField(record, inputKey);
|
|
16
|
+
if (value !== undefined)
|
|
17
|
+
metadata[outputKey] = value;
|
|
18
|
+
}
|
|
19
|
+
function errorMessage(error) {
|
|
20
|
+
return error instanceof Error ? error.message : String(error);
|
|
21
|
+
}
|
|
22
|
+
function ownerMetadataFromAgent(agent, params) {
|
|
23
|
+
const metadata = {};
|
|
24
|
+
addStringField(metadata, "updated_at", agent);
|
|
25
|
+
const agentId = stringField(agent, "user_id") ?? params.accountUserId;
|
|
26
|
+
if (agentId !== undefined)
|
|
27
|
+
metadata.agent_id = agentId;
|
|
28
|
+
const ownerId = stringField(agent, "owner_id") ?? params.accountOwnerUserId;
|
|
29
|
+
if (ownerId !== undefined)
|
|
30
|
+
metadata.owner_id = ownerId;
|
|
31
|
+
addStringField(metadata, "nickname", agent);
|
|
32
|
+
addStringField(metadata, "avatar_url", agent);
|
|
33
|
+
addStringField(metadata, "bio", agent);
|
|
34
|
+
addStringField(metadata, "behavior", agent);
|
|
35
|
+
return metadata;
|
|
36
|
+
}
|
|
37
|
+
function userMetadataFromRecord(record, targetUserId) {
|
|
38
|
+
const metadata = {};
|
|
39
|
+
addStringField(metadata, "updated_at", record);
|
|
40
|
+
metadata.id = targetUserId;
|
|
41
|
+
addStringField(metadata, "nickname", record);
|
|
42
|
+
addStringField(metadata, "avatar_url", record);
|
|
43
|
+
addStringField(metadata, "bio", record);
|
|
44
|
+
addStringField(metadata, "profile_type", record, "type");
|
|
45
|
+
return metadata;
|
|
46
|
+
}
|
|
47
|
+
function userMetadataFromProfile(profile, userId) {
|
|
48
|
+
const metadata = userMetadataFromRecord(profile, userId);
|
|
49
|
+
if (metadata === null) {
|
|
50
|
+
throw new Error("ClawChat user metadata response is missing id");
|
|
51
|
+
}
|
|
52
|
+
return metadata;
|
|
53
|
+
}
|
|
54
|
+
function participantUserMetadata(participant) {
|
|
55
|
+
const userRecord = asRecord(participant.user);
|
|
56
|
+
const source = userRecord ?? participant;
|
|
57
|
+
const userId = stringField(source, "id") ?? stringField(participant, "user_id");
|
|
58
|
+
if (!userId)
|
|
59
|
+
return null;
|
|
60
|
+
const metadata = userMetadataFromRecord(source, userId);
|
|
61
|
+
return metadata ? { userId, metadata } : null;
|
|
62
|
+
}
|
|
63
|
+
function groupMetadataFromConversation(conversation, groupId) {
|
|
64
|
+
const record = conversation;
|
|
65
|
+
const metadata = {};
|
|
66
|
+
addStringField(metadata, "updated_at", record);
|
|
67
|
+
metadata.id = groupId;
|
|
68
|
+
addStringField(metadata, "type", record);
|
|
69
|
+
addStringField(metadata, "title", record);
|
|
70
|
+
addStringField(metadata, "description", record);
|
|
71
|
+
addStringField(metadata, "creator_id", record);
|
|
72
|
+
addStringField(metadata, "created_at", record);
|
|
73
|
+
return metadata;
|
|
74
|
+
}
|
|
75
|
+
function pickUpdatePatch(patchInput, keys) {
|
|
76
|
+
const patch = {};
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
if (hasOwn(patchInput, key) && typeof patchInput[key] === "string") {
|
|
79
|
+
patch[key] = patchInput[key];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (Object.keys(patch).length === 0) {
|
|
83
|
+
throw new Error("ClawChat metadata update must include at least one mutable field");
|
|
84
|
+
}
|
|
85
|
+
return patch;
|
|
86
|
+
}
|
|
87
|
+
export async function pullOwnerMetadata(params) {
|
|
88
|
+
const getAgent = params.api.getAgentDetail ?? params.api.getAgentProfile;
|
|
89
|
+
if (!getAgent)
|
|
90
|
+
throw new Error("ClawChat metadata pull requires getAgentDetail");
|
|
91
|
+
if (!params.agentId)
|
|
92
|
+
throw new Error("ClawChat owner metadata pull requires agentId");
|
|
93
|
+
const data = await getAgent(params.agentId);
|
|
94
|
+
const metadata = ownerMetadataFromAgent(asRecord(data.agent) ?? {}, params);
|
|
95
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "owner", targetId: "owner" }, metadata);
|
|
96
|
+
return { ok: true, writes: [{ targetType: "owner", targetId: "owner" }], failures: [] };
|
|
97
|
+
}
|
|
98
|
+
export async function pullUserMetadata(params) {
|
|
99
|
+
const getUser = params.api.getUserProfile ?? params.api.getUserInfo;
|
|
100
|
+
if (!getUser)
|
|
101
|
+
throw new Error("ClawChat metadata pull requires getUserProfile");
|
|
102
|
+
const profile = await getUser(params.userId);
|
|
103
|
+
const metadata = userMetadataFromProfile(profile, params.userId);
|
|
104
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "user", targetId: params.userId }, metadata);
|
|
105
|
+
return { ok: true, writes: [{ targetType: "user", targetId: params.userId }], failures: [] };
|
|
106
|
+
}
|
|
107
|
+
export async function pullGroupMetadata(params) {
|
|
108
|
+
if (!params.api.getConversation)
|
|
109
|
+
throw new Error("ClawChat metadata pull requires getConversation");
|
|
110
|
+
const getUser = params.api.getUserProfile ?? params.api.getUserInfo;
|
|
111
|
+
const data = await params.api.getConversation(params.groupId);
|
|
112
|
+
const writes = [];
|
|
113
|
+
const failures = [];
|
|
114
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "group", targetId: params.groupId }, groupMetadataFromConversation(data.conversation, params.groupId));
|
|
115
|
+
writes.push({ targetType: "group", targetId: params.groupId });
|
|
116
|
+
const participants = Array.isArray(data.conversation.participants)
|
|
117
|
+
? data.conversation.participants
|
|
118
|
+
: [];
|
|
119
|
+
for (const participant of participants) {
|
|
120
|
+
const mapped = participantUserMetadata(participant);
|
|
121
|
+
if (!mapped)
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
const existing = await readClawChatMemoryFile(params.memoryRoot, {
|
|
125
|
+
targetType: "user",
|
|
126
|
+
targetId: mapped.userId,
|
|
127
|
+
});
|
|
128
|
+
if (existing.exists)
|
|
129
|
+
continue;
|
|
130
|
+
if (!getUser)
|
|
131
|
+
throw new Error("ClawChat participant metadata pull requires getUserProfile");
|
|
132
|
+
const profile = await getUser(mapped.userId);
|
|
133
|
+
const metadata = userMetadataFromProfile(profile, mapped.userId);
|
|
134
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "user", targetId: mapped.userId }, metadata);
|
|
135
|
+
writes.push({ targetType: "user", targetId: mapped.userId });
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
failures.push({ targetType: "user", targetId: mapped.userId, error: errorMessage(error) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { ok: failures.length === 0, writes, failures, conversation: data.conversation };
|
|
142
|
+
}
|
|
143
|
+
export async function pushMetadata(params) {
|
|
144
|
+
const file = await readClawChatMemoryFile(params.memoryRoot, {
|
|
145
|
+
targetType: params.targetType,
|
|
146
|
+
targetId: params.targetId,
|
|
147
|
+
});
|
|
148
|
+
return await updateMetadata({
|
|
149
|
+
memoryRoot: params.memoryRoot,
|
|
150
|
+
targetType: params.targetType,
|
|
151
|
+
targetId: params.targetId,
|
|
152
|
+
agentId: params.agentId,
|
|
153
|
+
accountUserId: params.accountUserId,
|
|
154
|
+
patch: pickPushPatch(params.targetType, params.targetId, file.metadata, {
|
|
155
|
+
fields: params.fields,
|
|
156
|
+
agentId: params.agentId,
|
|
157
|
+
accountUserId: params.accountUserId,
|
|
158
|
+
}),
|
|
159
|
+
api: params.api,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function mutableFieldsForTarget(targetType, targetId, params) {
|
|
163
|
+
if (targetType === "owner") {
|
|
164
|
+
if (targetId !== "owner")
|
|
165
|
+
throw new Error("ClawChat owner metadata targetId must be owner");
|
|
166
|
+
if (!params.agentId)
|
|
167
|
+
throw new Error("ClawChat owner metadata update requires agentId");
|
|
168
|
+
return ["nickname", "avatar_url", "bio", "behavior"];
|
|
169
|
+
}
|
|
170
|
+
if (targetType === "user") {
|
|
171
|
+
if (!params.accountUserId)
|
|
172
|
+
throw new Error("ClawChat user metadata update requires accountUserId");
|
|
173
|
+
if (targetId !== params.accountUserId) {
|
|
174
|
+
throw new Error("ClawChat user metadata update is allowed only for the connected user");
|
|
175
|
+
}
|
|
176
|
+
return ["nickname", "avatar_url", "bio"];
|
|
177
|
+
}
|
|
178
|
+
return ["title", "description"];
|
|
179
|
+
}
|
|
180
|
+
function pickPushPatch(targetType, targetId, metadata, params) {
|
|
181
|
+
if (!Array.isArray(params.fields) || params.fields.length === 0) {
|
|
182
|
+
throw new Error("fields are required for metadata push");
|
|
183
|
+
}
|
|
184
|
+
const allowed = mutableFieldsForTarget(targetType, targetId, params);
|
|
185
|
+
const patch = {};
|
|
186
|
+
for (const field of params.fields) {
|
|
187
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
188
|
+
throw new Error("fields must contain non-empty strings");
|
|
189
|
+
}
|
|
190
|
+
if (!allowed.includes(field)) {
|
|
191
|
+
throw new Error(`fields contain non-pushable metadata field: ${field}`);
|
|
192
|
+
}
|
|
193
|
+
if (!hasOwn(metadata, field)) {
|
|
194
|
+
throw new Error(`missing_metadata_field: ${field}`);
|
|
195
|
+
}
|
|
196
|
+
patch[field] = metadata[field];
|
|
197
|
+
}
|
|
198
|
+
return patch;
|
|
199
|
+
}
|
|
200
|
+
export async function updateMetadata(params) {
|
|
201
|
+
if (params.targetType === "owner") {
|
|
202
|
+
if (!params.agentId)
|
|
203
|
+
throw new Error("ClawChat owner metadata update requires agentId");
|
|
204
|
+
if (!params.api.patchAgent)
|
|
205
|
+
throw new Error("ClawChat owner metadata update requires patchAgent");
|
|
206
|
+
const patch = pickUpdatePatch(params.patch, ["nickname", "avatar_url", "bio", "behavior"]);
|
|
207
|
+
const response = await params.api.patchAgent(params.agentId, patch);
|
|
208
|
+
const metadata = ownerMetadataFromAgent(response.agent, {
|
|
209
|
+
accountUserId: params.accountUserId,
|
|
210
|
+
});
|
|
211
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "owner", targetId: "owner" }, metadata);
|
|
212
|
+
return { ok: true, writes: [{ targetType: "owner", targetId: "owner" }], failures: [], metadata };
|
|
213
|
+
}
|
|
214
|
+
if (params.targetType === "user") {
|
|
215
|
+
if (!params.accountUserId)
|
|
216
|
+
throw new Error("ClawChat user metadata update requires accountUserId");
|
|
217
|
+
if (params.targetId !== params.accountUserId) {
|
|
218
|
+
throw new Error("ClawChat user metadata update is allowed only for the connected user");
|
|
219
|
+
}
|
|
220
|
+
if (!params.api.updateMyProfile)
|
|
221
|
+
throw new Error("ClawChat user metadata update requires updateMyProfile");
|
|
222
|
+
const patch = pickUpdatePatch(params.patch, ["nickname", "avatar_url", "bio"]);
|
|
223
|
+
const profile = await params.api.updateMyProfile(patch);
|
|
224
|
+
const metadata = userMetadataFromProfile(profile, params.targetId);
|
|
225
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "user", targetId: params.targetId }, metadata);
|
|
226
|
+
return { ok: true, writes: [{ targetType: "user", targetId: params.targetId }], failures: [], metadata };
|
|
227
|
+
}
|
|
228
|
+
if (!params.api.patchConversation)
|
|
229
|
+
throw new Error("ClawChat group metadata update requires patchConversation");
|
|
230
|
+
const patch = pickUpdatePatch(params.patch, ["title", "description"]);
|
|
231
|
+
const response = await params.api.patchConversation(params.targetId, patch);
|
|
232
|
+
const metadata = groupMetadataFromConversation(response.conversation, params.targetId);
|
|
233
|
+
await writeClawChatMetadata(params.memoryRoot, { targetType: "group", targetId: params.targetId }, metadata);
|
|
234
|
+
return { ok: true, writes: [{ targetType: "group", targetId: params.targetId }], failures: [], metadata };
|
|
235
|
+
}
|
package/dist/src/client.js
CHANGED
|
@@ -1,74 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function createMsghubConnectTransport(account, transport, lifecycle) {
|
|
3
|
-
const maybeWrapped = transport;
|
|
4
|
-
if (maybeWrapped.__openclawInnerTransport)
|
|
5
|
-
return transport;
|
|
6
|
-
const wrapped = {
|
|
7
|
-
__openclawInnerTransport: transport,
|
|
8
|
-
get state() {
|
|
9
|
-
return transport.state;
|
|
10
|
-
},
|
|
11
|
-
connect(url, handlers) {
|
|
12
|
-
return transport.connect(url, handlers);
|
|
13
|
-
},
|
|
14
|
-
send(data) {
|
|
15
|
-
let parsed;
|
|
16
|
-
try {
|
|
17
|
-
parsed = JSON.parse(data);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
transport.send(data);
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
if (!parsed ||
|
|
24
|
-
typeof parsed !== "object" ||
|
|
25
|
-
parsed.event !== "connect") {
|
|
26
|
-
transport.send(data);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const env = parsed;
|
|
30
|
-
const nonce = typeof env.payload?.nonce === "string" ? env.payload.nonce : "";
|
|
31
|
-
const payload = {
|
|
32
|
-
token: account.token,
|
|
33
|
-
nonce,
|
|
34
|
-
...(account.userId ? { device_id: account.userId } : {}),
|
|
35
|
-
capabilities: {
|
|
36
|
-
multi_device: true,
|
|
37
|
-
device_replay: true,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
const connectEnv = { ...env, payload };
|
|
41
|
-
transport.send(JSON.stringify(connectEnv));
|
|
42
|
-
lifecycle?.onConnectFrameSent?.(connectEnv);
|
|
43
|
-
},
|
|
44
|
-
close(code, reason) {
|
|
45
|
-
transport.close(code, reason);
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
return wrapped;
|
|
49
|
-
}
|
|
50
|
-
function installMsghubConnectTransport(account, client, lifecycle) {
|
|
51
|
-
const inner = client;
|
|
52
|
-
if (!inner.opts?.transport)
|
|
53
|
-
return;
|
|
54
|
-
inner.opts.transport = createMsghubConnectTransport(account, inner.opts.transport, lifecycle);
|
|
55
|
-
}
|
|
1
|
+
import { createClawChatClient } from "./ws-client.js";
|
|
56
2
|
export function createOpenclawClawlingClient(account, overrides = {}) {
|
|
57
|
-
|
|
58
|
-
// is already unbounded, so omitting the field keeps that behavior. This
|
|
59
|
-
// avoids forcing the SDK to special-case `Infinity`.
|
|
60
|
-
const maxRetries = account.reconnect.maxRetries;
|
|
61
|
-
const reconnect = {
|
|
62
|
-
enabled: true,
|
|
63
|
-
initialDelay: account.reconnect.initialDelay,
|
|
64
|
-
maxDelay: account.reconnect.maxDelay,
|
|
65
|
-
jitterRatio: account.reconnect.jitterRatio,
|
|
66
|
-
...(Number.isFinite(maxRetries) ? { maxRetries } : {}),
|
|
67
|
-
};
|
|
68
|
-
const options = {
|
|
3
|
+
const client = createClawChatClient({
|
|
69
4
|
url: account.websocketUrl,
|
|
70
5
|
token: account.token,
|
|
71
|
-
|
|
6
|
+
deviceId: account.userId,
|
|
7
|
+
...(overrides.transport ? { transport: overrides.transport } : {}),
|
|
8
|
+
reconnect: {
|
|
9
|
+
enabled: true,
|
|
10
|
+
initialDelay: account.reconnect.initialDelay,
|
|
11
|
+
maxDelay: account.reconnect.maxDelay,
|
|
12
|
+
jitterRatio: account.reconnect.jitterRatio,
|
|
13
|
+
maxRetries: account.reconnect.maxRetries,
|
|
14
|
+
},
|
|
72
15
|
heartbeat: {
|
|
73
16
|
enabled: true,
|
|
74
17
|
interval: account.heartbeat.interval,
|
|
@@ -78,14 +21,16 @@ export function createOpenclawClawlingClient(account, overrides = {}) {
|
|
|
78
21
|
timeout: account.ack.timeout,
|
|
79
22
|
autoResendOnTimeout: account.ack.autoResendOnTimeout,
|
|
80
23
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
24
|
+
});
|
|
25
|
+
if (overrides.wsLifecycle?.onConnectFrameSent) {
|
|
26
|
+
const sendRawEnvelope = client.sendRawEnvelope.bind(client);
|
|
27
|
+
client.sendRawEnvelope = (env) => {
|
|
28
|
+
sendRawEnvelope(env);
|
|
29
|
+
if (env.event === "connect") {
|
|
30
|
+
overrides.wsLifecycle?.onConnectFrameSent?.(env);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
89
34
|
return client;
|
|
90
35
|
}
|
|
91
36
|
function normalizeRouting(params) {
|
|
@@ -97,31 +42,26 @@ function normalizeRouting(params) {
|
|
|
97
42
|
throw new Error("openclaw-clawchat streaming emit requires routing");
|
|
98
43
|
}
|
|
99
44
|
/**
|
|
100
|
-
* Emit a raw v2 envelope
|
|
101
|
-
* `chat_id` routing without
|
|
45
|
+
* Emit a raw v2 envelope through the local client so stream helpers carry
|
|
46
|
+
* top-level `chat_id` routing without legacy `to` metadata.
|
|
102
47
|
*/
|
|
103
48
|
function emitEnvelope(client, event, payload, routing, options = {}) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
inner.emitRaw(event, payload, { chat_id: routing.chatId });
|
|
49
|
+
if (!options.forceRawTransport) {
|
|
50
|
+
client.emitRaw(event, payload, { chat_id: routing.chatId });
|
|
107
51
|
return;
|
|
108
52
|
}
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
inner.emitRaw(event, payload, { chat_id: routing.chatId });
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
throw new Error("openclaw-clawchat streaming emit requires SDK raw transport");
|
|
53
|
+
if (typeof client.nextTraceId !== "function" || typeof client.sendRawEnvelope !== "function") {
|
|
54
|
+
throw new Error("openclaw-clawchat streaming emit requires local raw transport");
|
|
115
55
|
}
|
|
116
56
|
const env = {
|
|
117
57
|
version: "2",
|
|
118
58
|
event,
|
|
119
|
-
trace_id:
|
|
59
|
+
trace_id: client.nextTraceId(),
|
|
120
60
|
emitted_at: Date.now(),
|
|
121
61
|
chat_id: routing.chatId,
|
|
122
62
|
payload,
|
|
123
63
|
};
|
|
124
|
-
|
|
64
|
+
client.sendRawEnvelope(env);
|
|
125
65
|
}
|
|
126
66
|
/**
|
|
127
67
|
* Emit a minimal `message.created` envelope to open a streaming message.
|
|
@@ -189,10 +129,8 @@ export function emitStreamDone(client, params) {
|
|
|
189
129
|
* the same `payload.message_id` as the preceding `message.created` /
|
|
190
130
|
* `message.add` / `message.done` frames.
|
|
191
131
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* streaming-finalize use case the backend expects the correlated id, so we
|
|
195
|
-
* bypass the SDK validator and write directly to the transport.
|
|
132
|
+
* Final stream replies include the correlated `payload.message_id`, so they
|
|
133
|
+
* use the local raw-envelope API instead of any higher-level ackable send.
|
|
196
134
|
*/
|
|
197
135
|
export function emitFinalStreamReply(client, params) {
|
|
198
136
|
const routing = normalizeRouting(params);
|
package/dist/src/commands.js
CHANGED
|
@@ -7,14 +7,14 @@ function errorMessage(err) {
|
|
|
7
7
|
}
|
|
8
8
|
export function registerOpenclawClawlingCommands(api) {
|
|
9
9
|
api.registerCommand({
|
|
10
|
-
name: "clawchat-
|
|
11
|
-
description: "Activate ClawChat with an invite code, e.g. /clawchat-
|
|
10
|
+
name: "clawchat-activate",
|
|
11
|
+
description: "Activate ClawChat with an invite code, e.g. /clawchat-activate A1B2C3.",
|
|
12
12
|
acceptsArgs: true,
|
|
13
13
|
requireAuth: true,
|
|
14
14
|
async handler(ctx) {
|
|
15
15
|
const code = extractInviteCode(ctx.args ?? ctx.commandBody);
|
|
16
16
|
if (!code) {
|
|
17
|
-
return { text: "ClawChat invite code is required. Usage: /clawchat-
|
|
17
|
+
return { text: "ClawChat invite code is required. Usage: /clawchat-activate A1B2C3" };
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
20
|
const { runOpenclawClawlingLogin } = await import("./login.runtime.js");
|
package/dist/src/config.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup";
|
|
2
2
|
export const CHANNEL_ID = "openclaw-clawchat";
|
|
3
3
|
export const CLAWCHAT_TOKEN_ENV = "CLAWCHAT_TOKEN";
|
|
4
|
+
export const CLAWCHAT_AGENT_ID_ENV = "CLAWCHAT_AGENT_ID";
|
|
4
5
|
export const CLAWCHAT_USER_ID_ENV = "CLAWCHAT_USER_ID";
|
|
6
|
+
export const CLAWCHAT_OWNER_USER_ID_ENV = "CLAWCHAT_OWNER_USER_ID";
|
|
5
7
|
export const CLAWCHAT_REFRESH_TOKEN_ENV = "CLAWCHAT_REFRESH_TOKEN";
|
|
6
8
|
export const CLAWCHAT_BASE_URL_ENV = "CLAWCHAT_BASE_URL";
|
|
7
9
|
export const CLAWCHAT_WEBSOCKET_URL_ENV = "CLAWCHAT_WEBSOCKET_URL";
|
|
@@ -51,9 +53,23 @@ export const openclawClawlingConfigSchema = {
|
|
|
51
53
|
baseUrl: { type: "string" },
|
|
52
54
|
token: { type: "string" },
|
|
53
55
|
refreshToken: { type: "string" },
|
|
56
|
+
agentId: { type: "string" },
|
|
54
57
|
userId: { type: "string" },
|
|
58
|
+
ownerUserId: { type: "string" },
|
|
55
59
|
replyMode: { type: "string", enum: ["static", "stream"] },
|
|
56
60
|
groupMode: { type: "string", enum: ["mention", "all"] },
|
|
61
|
+
groupCommandMode: { type: "string", enum: ["owner", "all", "off"] },
|
|
62
|
+
groups: {
|
|
63
|
+
type: "object",
|
|
64
|
+
additionalProperties: {
|
|
65
|
+
type: "object",
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
properties: {
|
|
68
|
+
groupMode: { type: "string", enum: ["mention", "all"] },
|
|
69
|
+
groupCommandMode: { type: "string", enum: ["owner", "all", "off"] },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
57
73
|
forwardThinking: { type: "boolean" },
|
|
58
74
|
forwardToolCalls: { type: "boolean" },
|
|
59
75
|
richInteractions: { type: "boolean" },
|
|
@@ -165,10 +181,41 @@ function readEnvString(env, key) {
|
|
|
165
181
|
return readOptionalString(env[key]);
|
|
166
182
|
}
|
|
167
183
|
function readReplyMode(value) {
|
|
168
|
-
return value === "
|
|
184
|
+
return value === "static" ? "static" : "stream";
|
|
169
185
|
}
|
|
170
186
|
function readGroupMode(value) {
|
|
171
|
-
return value === "
|
|
187
|
+
return value === "mention" ? "mention" : "all";
|
|
188
|
+
}
|
|
189
|
+
function readGroupCommandMode(value) {
|
|
190
|
+
return value === "all" || value === "off" ? value : "owner";
|
|
191
|
+
}
|
|
192
|
+
function readGroups(value) {
|
|
193
|
+
const rawGroups = value && typeof value === "object" && !Array.isArray(value)
|
|
194
|
+
? value
|
|
195
|
+
: {};
|
|
196
|
+
const groups = {};
|
|
197
|
+
for (const [chatId, rawGroup] of Object.entries(rawGroups)) {
|
|
198
|
+
if (!chatId)
|
|
199
|
+
continue;
|
|
200
|
+
const group = rawGroup && typeof rawGroup === "object" && !Array.isArray(rawGroup)
|
|
201
|
+
? rawGroup
|
|
202
|
+
: {};
|
|
203
|
+
groups[chatId] = {
|
|
204
|
+
groupMode: readGroupMode(group.groupMode),
|
|
205
|
+
groupCommandMode: readGroupCommandMode(group.groupCommandMode),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return groups;
|
|
209
|
+
}
|
|
210
|
+
export function effectiveGroupMode(account, chatId) {
|
|
211
|
+
return account.groups[chatId]?.groupMode
|
|
212
|
+
?? account.groups["*"]?.groupMode
|
|
213
|
+
?? account.groupMode;
|
|
214
|
+
}
|
|
215
|
+
export function effectiveGroupCommandMode(account, chatId) {
|
|
216
|
+
return account.groups[chatId]?.groupCommandMode
|
|
217
|
+
?? account.groups["*"]?.groupCommandMode
|
|
218
|
+
?? account.groupCommandMode;
|
|
172
219
|
}
|
|
173
220
|
function readStream(raw) {
|
|
174
221
|
const s = raw && typeof raw === "object" ? raw : {};
|
|
@@ -215,10 +262,14 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
215
262
|
readEnvString(env, CLAWCHAT_BASE_URL_ENV) ||
|
|
216
263
|
DEFAULT_BASE_URL;
|
|
217
264
|
const token = readOptionalString(channel.token) || readEnvString(env, CLAWCHAT_TOKEN_ENV);
|
|
265
|
+
const agentId = readOptionalString(channel.agentId) || readEnvString(env, CLAWCHAT_AGENT_ID_ENV);
|
|
218
266
|
const userId = readOptionalString(channel.userId) || readEnvString(env, CLAWCHAT_USER_ID_ENV);
|
|
267
|
+
const ownerUserId = readOptionalString(channel.ownerUserId) || readEnvString(env, CLAWCHAT_OWNER_USER_ID_ENV);
|
|
219
268
|
const enabled = typeof channel.enabled === "boolean" ? channel.enabled : true;
|
|
220
269
|
const replyMode = readReplyMode(channel.replyMode);
|
|
221
270
|
const groupMode = readGroupMode(channel.groupMode);
|
|
271
|
+
const groupCommandMode = readGroupCommandMode(channel.groupCommandMode);
|
|
272
|
+
const groups = readGroups(channel.groups);
|
|
222
273
|
const forwardThinking = typeof channel.forwardThinking === "boolean" ? channel.forwardThinking : true;
|
|
223
274
|
const forwardToolCalls = typeof channel.forwardToolCalls === "boolean" ? channel.forwardToolCalls : false;
|
|
224
275
|
const richInteractions = typeof channel.richInteractions === "boolean" ? channel.richInteractions : false;
|
|
@@ -226,13 +277,17 @@ export function resolveOpenclawClawlingAccount(cfg, env = process.env) {
|
|
|
226
277
|
accountId: DEFAULT_ACCOUNT_ID,
|
|
227
278
|
name: CHANNEL_ID,
|
|
228
279
|
enabled,
|
|
229
|
-
configured: Boolean(websocketUrl && token && userId),
|
|
280
|
+
configured: Boolean(websocketUrl && token && userId && ownerUserId),
|
|
230
281
|
websocketUrl,
|
|
231
282
|
baseUrl,
|
|
232
283
|
token,
|
|
284
|
+
agentId,
|
|
233
285
|
userId,
|
|
286
|
+
ownerUserId,
|
|
234
287
|
replyMode,
|
|
235
288
|
groupMode,
|
|
289
|
+
groupCommandMode,
|
|
290
|
+
groups,
|
|
236
291
|
forwardThinking,
|
|
237
292
|
forwardToolCalls,
|
|
238
293
|
richInteractions,
|
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
function formatSenderRelation(turn) {
|
|
10
|
+
return turn.senderRelation || "peer_user";
|
|
11
|
+
}
|
|
12
|
+
function formatSenderProfileType(turn) {
|
|
13
|
+
if (turn.senderProfileType)
|
|
14
|
+
return turn.senderProfileType;
|
|
15
|
+
const relation = formatSenderRelation(turn);
|
|
16
|
+
return relation === "self_agent" || relation === "peer_agent" ? "agent" : "user";
|
|
17
|
+
}
|
|
18
|
+
function formatMessageBody(rawBody) {
|
|
19
|
+
return rawBody || "(empty message)";
|
|
20
|
+
}
|
|
21
|
+
function formatField(value) {
|
|
22
|
+
return value.replace(/\\/g, "\\\\").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
|
|
23
|
+
}
|
|
24
|
+
function formatMentionedUserIds(turn) {
|
|
25
|
+
return turn.mentionedUserIds.length ? turn.mentionedUserIds.join(",") : "-";
|
|
26
|
+
}
|
|
27
|
+
export function formatCoalescedGroupBody(turns, timing = { idleSeconds: 10, maxWaitSeconds: 30 }) {
|
|
28
|
+
const header = `ClawChat group batch (${turns.length} ${turns.length === 1 ? "message" : "messages"}, ${timing.idleSeconds}s idle, ${timing.maxWaitSeconds}s max):`;
|
|
29
|
+
return [
|
|
30
|
+
header,
|
|
31
|
+
turns.map((turn) => {
|
|
32
|
+
const senderName = turn.senderNickName || turn.senderId;
|
|
33
|
+
const senderIsOwner = turn.senderIsOwner ?? formatSenderRelation(turn) === "owner";
|
|
34
|
+
return [
|
|
35
|
+
"[message]",
|
|
36
|
+
`sender_id: ${formatField(turn.senderId)}`,
|
|
37
|
+
`sender_name: ${formatField(senderName)}`,
|
|
38
|
+
`sender_profile_type: ${formatField(formatSenderProfileType(turn))}`,
|
|
39
|
+
`sender_is_owner: ${senderIsOwner ? "true" : "false"}`,
|
|
40
|
+
`mentions_current_agent: ${turn.wasMentioned ? "true" : "false"}`,
|
|
41
|
+
`mentioned_user_ids: ${formatField(formatMentionedUserIds(turn))}`,
|
|
42
|
+
"text:",
|
|
43
|
+
formatMessageBody(turn.rawBody),
|
|
44
|
+
].join("\n");
|
|
45
|
+
}).join("\n\n"),
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
export function mergeGroupTurns(turns, timing = { idleSeconds: 10, maxWaitSeconds: 30 }) {
|
|
49
|
+
if (turns.length === 0)
|
|
50
|
+
throw new Error("cannot merge empty group turn batch");
|
|
51
|
+
const latest = turns[turns.length - 1];
|
|
52
|
+
return {
|
|
53
|
+
...latest,
|
|
54
|
+
rawBody: formatCoalescedGroupBody(turns, timing),
|
|
55
|
+
mediaItems: turns.flatMap((turn) => turn.mediaItems),
|
|
56
|
+
wasMentioned: turns.some((turn) => turn.wasMentioned),
|
|
57
|
+
mentionedUserIds: Array.from(new Set(turns.flatMap((turn) => turn.mentionedUserIds))),
|
|
58
|
+
coalescedGroupBatch: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function createGroupMessageCoalescer(params) {
|
|
62
|
+
const pending = new Map();
|
|
63
|
+
const timing = {
|
|
64
|
+
idleSeconds: Math.round(params.idleMs / 1000),
|
|
65
|
+
maxWaitSeconds: Math.round(params.maxWaitMs / 1000),
|
|
66
|
+
};
|
|
67
|
+
const flush = (chatId) => {
|
|
68
|
+
const batch = pending.get(chatId);
|
|
69
|
+
if (!batch)
|
|
70
|
+
return;
|
|
71
|
+
pending.delete(chatId);
|
|
72
|
+
clearTimeout(batch.idleTimer);
|
|
73
|
+
clearTimeout(batch.maxWaitTimer);
|
|
74
|
+
void params.dispatch(mergeGroupTurns(batch.turns, timing)).catch((error) => {
|
|
75
|
+
params.onError?.(error);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
enqueue(turn) {
|
|
80
|
+
const chatId = turn.peer.id;
|
|
81
|
+
const existing = pending.get(chatId);
|
|
82
|
+
if (existing) {
|
|
83
|
+
existing.turns.push(turn);
|
|
84
|
+
clearTimeout(existing.idleTimer);
|
|
85
|
+
existing.idleTimer = setTimeout(() => flush(chatId), params.idleMs);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const idleTimer = setTimeout(() => flush(chatId), params.idleMs);
|
|
89
|
+
const maxWaitTimer = setTimeout(() => flush(chatId), params.maxWaitMs);
|
|
90
|
+
pending.set(chatId, { turns: [turn], idleTimer, maxWaitTimer });
|
|
91
|
+
},
|
|
92
|
+
flushNow(chatId) {
|
|
93
|
+
flush(chatId);
|
|
94
|
+
},
|
|
95
|
+
cancelAll() {
|
|
96
|
+
for (const [chatId, batch] of pending) {
|
|
97
|
+
clearTimeout(batch.idleTimer);
|
|
98
|
+
clearTimeout(batch.maxWaitTimer);
|
|
99
|
+
params.onDrop?.(chatId, batch.turns.length);
|
|
100
|
+
}
|
|
101
|
+
pending.clear();
|
|
102
|
+
},
|
|
103
|
+
pendingCount(chatId) {
|
|
104
|
+
return pending.get(chatId)?.turns.length ?? 0;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|