@newbase-clawchat/openclaw-clawchat 2026.5.4 → 2026.5.12-13
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/INSTALL.md +64 -0
- package/README.md +121 -19
- package/dist/index.js +10 -19
- package/dist/setup-entry.js +3 -0
- package/dist/src/api-client.js +78 -10
- package/dist/src/api-types.test-d.js +10 -0
- package/dist/src/channel.js +25 -156
- package/dist/src/channel.setup.js +120 -0
- package/dist/src/client.js +37 -41
- package/dist/src/config.js +75 -17
- package/dist/src/inbound.js +79 -61
- package/dist/src/login.runtime.js +84 -19
- package/dist/src/media-runtime.js +8 -8
- package/dist/src/message-mapper.js +1 -1
- package/dist/src/mock-transport.js +31 -0
- package/dist/src/outbound.js +410 -26
- package/dist/src/protocol-types.js +63 -0
- package/dist/src/protocol-types.typecheck.js +1 -0
- package/dist/src/protocol.js +2 -7
- package/dist/src/reply-dispatcher.js +157 -54
- package/dist/src/runtime.js +795 -119
- package/dist/src/storage.js +689 -0
- package/dist/src/tools-schema.js +98 -16
- package/dist/src/tools.js +422 -135
- package/dist/src/ws-alignment.js +178 -0
- package/dist/src/ws-client.js +588 -0
- package/dist/src/ws-log.js +19 -0
- package/index.ts +10 -22
- package/openclaw.plugin.json +37 -2
- package/package.json +17 -4
- package/setup-entry.ts +4 -0
- package/skills/clawchat/SKILL.md +88 -0
- package/src/api-client.test.ts +274 -14
- package/src/api-client.ts +138 -23
- package/src/api-types.test-d.ts +12 -0
- package/src/api-types.ts +90 -4
- package/src/buffered-stream.test.ts +14 -12
- package/src/buffered-stream.ts +1 -1
- package/src/channel.outbound.test.ts +269 -60
- package/src/channel.setup.ts +146 -0
- package/src/channel.test.ts +130 -24
- package/src/channel.ts +30 -186
- package/src/client.test.ts +197 -11
- package/src/client.ts +50 -57
- package/src/config.test.ts +108 -6
- package/src/config.ts +95 -24
- package/src/inbound.test.ts +288 -37
- package/src/inbound.ts +96 -84
- package/src/login.runtime.test.ts +347 -13
- package/src/login.runtime.ts +105 -23
- package/src/manifest.test.ts +146 -74
- package/src/media-runtime.test.ts +57 -2
- package/src/media-runtime.ts +26 -17
- 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 +694 -73
- package/src/outbound.ts +484 -31
- package/src/plugin-entry.test.ts +1 -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.test.ts +1 -6
- package/src/protocol.ts +2 -7
- package/src/reply-dispatcher.test.ts +819 -119
- package/src/reply-dispatcher.ts +202 -60
- package/src/runtime.test.ts +2120 -41
- package/src/runtime.ts +935 -142
- package/src/scripts.test.ts +85 -0
- package/src/storage.test.ts +793 -0
- package/src/storage.ts +1095 -0
- package/src/streaming.test.ts +9 -8
- package/src/streaming.ts +1 -1
- package/src/tools-schema.ts +148 -20
- package/src/tools.test.ts +377 -50
- package/src/tools.ts +574 -154
- package/src/ws-alignment.test.ts +103 -0
- package/src/ws-alignment.ts +275 -0
- package/src/ws-client.test.ts +1218 -0
- package/src/ws-client.ts +662 -0
- package/src/ws-log.test.ts +32 -0
- package/src/ws-log.ts +31 -0
- package/skills/clawchat-account-tools/SKILL.md +0 -26
- package/skills/clawchat-activate/SKILL.md +0 -47
package/src/inbound.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EVENT,
|
|
3
3
|
type ChatType,
|
|
4
|
-
type DownlinkMessageSendPayload,
|
|
5
4
|
type Envelope,
|
|
6
|
-
} from "
|
|
5
|
+
} from "./protocol-types.ts";
|
|
7
6
|
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/core";
|
|
8
|
-
import type
|
|
7
|
+
import { effectiveGroupMode, type ResolvedOpenclawClawlingAccount } from "./config.ts";
|
|
9
8
|
import type { MediaItem } from "./media-runtime.ts";
|
|
10
9
|
import { extractMediaFragments, fragmentsToText } from "./message-mapper.ts";
|
|
11
10
|
import { hasRenderableText, isInboundMessagePayload } from "./protocol.ts";
|
|
@@ -37,11 +36,11 @@ export interface IngestTurnParams {
|
|
|
37
36
|
cfg: OpenClawConfig;
|
|
38
37
|
runtime: PluginRuntime;
|
|
39
38
|
account: ResolvedOpenclawClawlingAccount;
|
|
40
|
-
envelope: Envelope<
|
|
39
|
+
envelope: Envelope<unknown>;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
export interface DispatchInboundParams {
|
|
44
|
-
envelope: Envelope<
|
|
43
|
+
envelope: Envelope<unknown>;
|
|
45
44
|
cfg: OpenClawConfig;
|
|
46
45
|
runtime: PluginRuntime;
|
|
47
46
|
account: ResolvedOpenclawClawlingAccount;
|
|
@@ -49,111 +48,140 @@ export interface DispatchInboundParams {
|
|
|
49
48
|
log?: { info?: (m: string) => void; error?: (m: string) => void };
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
const DEDUP_MAX = 256;
|
|
53
|
-
const dedupSeen: string[] = [];
|
|
54
|
-
const dedupSet = new Set<string>();
|
|
55
|
-
|
|
56
51
|
type SenderLike = {
|
|
57
52
|
id?: unknown;
|
|
58
53
|
nick_name?: unknown;
|
|
59
|
-
sender_id?: unknown;
|
|
60
|
-
display_name?: unknown;
|
|
61
54
|
type?: unknown;
|
|
62
55
|
};
|
|
63
56
|
|
|
64
|
-
function normalizeSender(sender: unknown): { id: string; nickName: string
|
|
57
|
+
function normalizeSender(sender: unknown): { id: string; nickName: string } | null {
|
|
65
58
|
if (!sender || typeof sender !== "object") return null;
|
|
66
59
|
const s = sender as SenderLike;
|
|
67
|
-
const id = typeof s.id === "string" ? s.id :
|
|
60
|
+
const id = typeof s.id === "string" ? s.id : "";
|
|
68
61
|
if (!id) return null;
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
typeof s.nick_name === "string"
|
|
72
|
-
? s.nick_name
|
|
73
|
-
: typeof s.display_name === "string"
|
|
74
|
-
? s.display_name
|
|
75
|
-
: id;
|
|
76
|
-
return { id, nickName, ...(type ? { type } : {}) };
|
|
62
|
+
const nickName = typeof s.nick_name === "string" ? s.nick_name : id;
|
|
63
|
+
return { id, nickName };
|
|
77
64
|
}
|
|
78
65
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
function isStreamDonePayload(payload: unknown): payload is {
|
|
67
|
+
message_id: string;
|
|
68
|
+
fragments: Array<Record<string, unknown>>;
|
|
69
|
+
} {
|
|
70
|
+
if (!payload || typeof payload !== "object") return false;
|
|
71
|
+
const p = payload as Record<string, unknown>;
|
|
72
|
+
if (typeof p.message_id !== "string" || p.message_id.length === 0) return false;
|
|
73
|
+
if (!Array.isArray(p.fragments)) return false;
|
|
74
|
+
if (p.streaming !== undefined && p.streaming !== null) {
|
|
75
|
+
if (typeof p.streaming !== "object") return false;
|
|
76
|
+
if ((p.streaming as { status?: unknown }).status !== "done") return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
function requireChatId(envelope: Envelope<unknown>): string | null {
|
|
82
|
+
const chatId = (envelope as Envelope<unknown> & { chat_id?: unknown }).chat_id;
|
|
83
|
+
return typeof chatId === "string" && chatId.trim() ? chatId : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function extractMentionIds(fragments: Array<Record<string, unknown>>): string[] {
|
|
87
|
+
return fragments
|
|
88
|
+
.map((fragment) => fragment.kind === "mention" ? fragment.user_id : undefined)
|
|
89
|
+
.filter((userId): userId is string => typeof userId === "string" && userId.length > 0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeMentionIds(mentions: unknown[]): string[] {
|
|
93
|
+
return mentions
|
|
94
|
+
.map((mention) => {
|
|
95
|
+
if (typeof mention === "string") return mention;
|
|
96
|
+
if (mention && typeof mention === "object") {
|
|
97
|
+
const userId = (mention as { user_id?: unknown }).user_id;
|
|
98
|
+
return typeof userId === "string" ? userId : undefined;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
})
|
|
102
|
+
.filter((userId): userId is string => typeof userId === "string" && userId.length > 0);
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
/**
|
|
96
|
-
* Exported for direct unit testing.
|
|
97
|
-
*
|
|
98
|
-
* `mentions` branch is exercised by tests now so the group-enable change is
|
|
99
|
-
* a one-line filter removal later.
|
|
106
|
+
* Exported for direct unit testing. Direct chats always count as addressed;
|
|
107
|
+
* group chats require a mention unless config opts into all group messages.
|
|
100
108
|
*/
|
|
101
109
|
export function detectMention(params: {
|
|
102
|
-
mentions:
|
|
103
|
-
|
|
110
|
+
mentions: unknown[];
|
|
111
|
+
chatType: "direct" | "group";
|
|
104
112
|
userId: string;
|
|
105
113
|
}): boolean {
|
|
106
|
-
if (params.
|
|
107
|
-
return params.mentions.includes(params.userId);
|
|
114
|
+
if (params.chatType === "direct") return true;
|
|
115
|
+
return normalizeMentionIds(params.mentions).includes(params.userId);
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
export async function dispatchOpenclawClawlingInbound(
|
|
111
119
|
params: DispatchInboundParams,
|
|
112
120
|
): Promise<void> {
|
|
113
121
|
const { envelope, account, log } = params;
|
|
114
|
-
|
|
122
|
+
const isMaterializedMessage = envelope.event === EVENT.MESSAGE_SEND || envelope.event === EVENT.MESSAGE_REPLY;
|
|
123
|
+
const isStreamDone = envelope.event === "message.done";
|
|
124
|
+
if (!isMaterializedMessage && !isStreamDone) {
|
|
125
|
+
log?.info?.(
|
|
126
|
+
`[${account.accountId}] openclaw-clawchat skip non-business event=${envelope.event} trace=${envelope.trace_id}`,
|
|
127
|
+
);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (isMaterializedMessage && !isInboundMessagePayload(envelope.payload)) {
|
|
115
131
|
log?.info?.(
|
|
116
132
|
`[${account.accountId}] openclaw-clawchat skip: invalid payload trace=${envelope.trace_id}`,
|
|
117
133
|
);
|
|
118
134
|
return;
|
|
119
135
|
}
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
if (isStreamDone && !isStreamDonePayload(envelope.payload)) {
|
|
137
|
+
log?.info?.(
|
|
138
|
+
`[${account.accountId}] openclaw-clawchat skip: invalid stream payload trace=${envelope.trace_id}`,
|
|
139
|
+
);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const chatId = requireChatId(envelope);
|
|
143
|
+
if (!chatId) {
|
|
144
|
+
log?.info?.(
|
|
145
|
+
`[${account.accountId}] openclaw-clawchat skip: missing chat_id trace=${envelope.trace_id}`,
|
|
146
|
+
);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const payload = envelope.payload as {
|
|
150
|
+
message_id: string;
|
|
151
|
+
message_mode?: string;
|
|
152
|
+
message?: unknown;
|
|
153
|
+
fragments?: Array<Record<string, unknown>>;
|
|
154
|
+
};
|
|
155
|
+
const message = (isMaterializedMessage
|
|
156
|
+
? payload.message
|
|
157
|
+
: {
|
|
158
|
+
body: { fragments: payload.fragments ?? [] },
|
|
159
|
+
context: { mentions: extractMentionIds(payload.fragments ?? []), reply: null },
|
|
160
|
+
}) as {
|
|
122
161
|
body: { fragments: Array<Record<string, unknown>> };
|
|
123
162
|
context: {
|
|
124
|
-
mentions:
|
|
163
|
+
mentions: unknown[];
|
|
125
164
|
reply: {
|
|
126
165
|
reply_to_msg_id: string;
|
|
127
166
|
reply_preview: {
|
|
128
167
|
id?: string;
|
|
129
168
|
nick_name?: string;
|
|
130
|
-
sender_id?: string;
|
|
131
|
-
display_name?: string;
|
|
132
169
|
fragments: Array<Record<string, unknown>>;
|
|
133
170
|
};
|
|
134
171
|
} | null;
|
|
135
172
|
};
|
|
136
|
-
/** Legacy fallback: older fixtures carried sender inside payload.message. */
|
|
137
|
-
sender?: SenderLike;
|
|
138
173
|
};
|
|
139
174
|
|
|
140
|
-
|
|
141
|
-
// message.sender shape is accepted as a fallback for older fixtures.
|
|
142
|
-
const sender = normalizeSender(envelope.sender ?? message.sender);
|
|
175
|
+
const sender = normalizeSender(envelope.sender);
|
|
143
176
|
if (!sender) {
|
|
144
177
|
log?.info?.(
|
|
145
178
|
`[${account.accountId}] openclaw-clawchat skip: missing sender trace=${envelope.trace_id}`,
|
|
146
179
|
);
|
|
147
180
|
return;
|
|
148
181
|
}
|
|
149
|
-
|
|
150
|
-
// if the server didn't include it (defensive; shouldn't happen in practice).
|
|
151
|
-
const legacyTo = (envelope as Envelope<DownlinkMessageSendPayload> & {
|
|
152
|
-
to?: { type?: ChatType };
|
|
153
|
-
}).to;
|
|
154
|
-
const chatType: ChatType = envelope.chat_type ?? sender.type ?? legacyTo?.type ?? "direct";
|
|
182
|
+
const chatType: ChatType = envelope.chat_type === "group" ? "group" : "direct";
|
|
155
183
|
const isGroup = chatType === "group";
|
|
156
|
-
if (payload.message_mode !== "normal") {
|
|
184
|
+
if (isMaterializedMessage && payload.message_mode !== "normal") {
|
|
157
185
|
log?.info?.(
|
|
158
186
|
`[${account.accountId}] openclaw-clawchat skip non-normal mode=${payload.message_mode}`,
|
|
159
187
|
);
|
|
@@ -165,27 +193,22 @@ export async function dispatchOpenclawClawlingInbound(
|
|
|
165
193
|
);
|
|
166
194
|
return;
|
|
167
195
|
}
|
|
168
|
-
|
|
169
|
-
log?.info?.(
|
|
170
|
-
`[${account.accountId}] openclaw-clawchat skip duplicate msg=${payload.message_id}`,
|
|
171
|
-
);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
196
|
+
const mentionIds = normalizeMentionIds(message.context.mentions);
|
|
175
197
|
const rawBody = fragmentsToText(message.body.fragments as never, {
|
|
176
|
-
mentionFallbackIds:
|
|
198
|
+
mentionFallbackIds: mentionIds,
|
|
177
199
|
});
|
|
178
200
|
const mediaItems = extractMediaFragments(message.body.fragments as never);
|
|
179
201
|
const wasMentioned = detectMention({
|
|
180
|
-
mentions:
|
|
181
|
-
|
|
202
|
+
mentions: mentionIds,
|
|
203
|
+
chatType,
|
|
182
204
|
userId: account.userId,
|
|
183
205
|
});
|
|
184
206
|
|
|
185
207
|
// Group trigger policy: in "mention" mode we only handle group messages
|
|
186
208
|
// that @-mention us; "all" listens open and processes every group msg.
|
|
187
209
|
// Direct chats are unaffected (detectMention returns true).
|
|
188
|
-
|
|
210
|
+
const groupMode = isGroup ? effectiveGroupMode(account, chatId) : account.groupMode;
|
|
211
|
+
if (isGroup && groupMode === "mention" && !wasMentioned) {
|
|
189
212
|
log?.info?.(
|
|
190
213
|
`[${account.accountId}] openclaw-clawchat skip group (no mention) msg=${payload.message_id}`,
|
|
191
214
|
);
|
|
@@ -193,26 +216,15 @@ export async function dispatchOpenclawClawlingInbound(
|
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
log?.info?.(
|
|
196
|
-
`[${account.accountId}] openclaw-clawchat inbound event=${envelope.event
|
|
219
|
+
`[${account.accountId}] openclaw-clawchat inbound event=${envelope.event} msg=${payload.message_id} from=${sender.id} text_len=${rawBody.length} mentioned=${wasMentioned}`,
|
|
197
220
|
);
|
|
198
221
|
|
|
199
|
-
// New protocol: `chat_id` is the routing primary; `to` is deprecated.
|
|
200
|
-
// Fall back to sender.id if neither is present (defensive).
|
|
201
|
-
const chatId =
|
|
202
|
-
(envelope as Envelope<DownlinkMessageSendPayload> & { chat_id?: string }).chat_id ??
|
|
203
|
-
sender.id;
|
|
204
222
|
const replyCtx = message.context.reply
|
|
205
223
|
? {
|
|
206
224
|
replyToMessageId: message.context.reply.reply_to_msg_id,
|
|
207
225
|
replyPreviewChatId: chatId,
|
|
208
|
-
replyPreviewSenderId:
|
|
209
|
-
|
|
210
|
-
message.context.reply.reply_preview.sender_id ??
|
|
211
|
-
"",
|
|
212
|
-
replyPreviewNickName:
|
|
213
|
-
message.context.reply.reply_preview.nick_name ??
|
|
214
|
-
message.context.reply.reply_preview.display_name ??
|
|
215
|
-
"",
|
|
226
|
+
replyPreviewSenderId: message.context.reply.reply_preview.id ?? "",
|
|
227
|
+
replyPreviewNickName: message.context.reply.reply_preview.nick_name ?? "",
|
|
216
228
|
replyPreviewText: fragmentsToText(message.context.reply.reply_preview.fragments as never),
|
|
217
229
|
}
|
|
218
230
|
: undefined;
|