@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, expectTypeOf, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
EVENT,
|
|
4
|
+
isBusinessDispatchEvent,
|
|
5
|
+
type Envelope,
|
|
6
|
+
type Fragment,
|
|
7
|
+
type MessagePayload,
|
|
8
|
+
type ProtocolFragment,
|
|
9
|
+
type Sender,
|
|
10
|
+
type StreamDonePayload,
|
|
11
|
+
} from "./protocol-types.ts";
|
|
12
|
+
|
|
13
|
+
describe("clawchat local protocol types", () => {
|
|
14
|
+
it("declares protocol v2 event constants used by runtime dispatch", () => {
|
|
15
|
+
expect(EVENT.MESSAGE_SEND).toBe("message.send");
|
|
16
|
+
expect(EVENT.MESSAGE_REPLY).toBe("message.reply");
|
|
17
|
+
expect(EVENT.MESSAGE_DONE).toBe("message.done");
|
|
18
|
+
expect(EVENT.TYPING_UPDATE).toBe("typing.update");
|
|
19
|
+
expect(EVENT.PING).toBe("ping");
|
|
20
|
+
expect(EVENT.PONG).toBe("pong");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("treats message.send, message.reply, and message.done as agent-dispatch events", () => {
|
|
24
|
+
expect(isBusinessDispatchEvent(EVENT.MESSAGE_SEND)).toBe(true);
|
|
25
|
+
expect(isBusinessDispatchEvent(EVENT.MESSAGE_REPLY)).toBe(true);
|
|
26
|
+
expect(isBusinessDispatchEvent(EVENT.MESSAGE_DONE)).toBe(true);
|
|
27
|
+
expect(isBusinessDispatchEvent(EVENT.MESSAGE_ADD)).toBe(false);
|
|
28
|
+
expect(isBusinessDispatchEvent(EVENT.PONG)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("models typing.update with is_typing payload", () => {
|
|
32
|
+
const env: Envelope<{ is_typing: boolean }> = {
|
|
33
|
+
version: "2",
|
|
34
|
+
event: EVENT.TYPING_UPDATE,
|
|
35
|
+
trace_id: "trace-typing",
|
|
36
|
+
emitted_at: 1776162600000,
|
|
37
|
+
chat_id: "chat-1",
|
|
38
|
+
payload: { is_typing: true },
|
|
39
|
+
};
|
|
40
|
+
expect(env.payload.is_typing).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("models sender as a downlink user shape with required direct type", () => {
|
|
44
|
+
expectTypeOf<Sender["type"]>().toEqualTypeOf<"direct">();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("models materialized message payloads with optional uplink message IDs", () => {
|
|
48
|
+
expectTypeOf<{
|
|
49
|
+
message_mode: string;
|
|
50
|
+
message: MessagePayload["message"];
|
|
51
|
+
}>().toMatchTypeOf<MessagePayload>();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("models reply previews with required identity and local fragments", () => {
|
|
55
|
+
type ReplyContext = NonNullable<MessagePayload["message"]["context"]["reply"]>;
|
|
56
|
+
type ReplyPreview = NonNullable<ReplyContext["reply_preview"]>;
|
|
57
|
+
|
|
58
|
+
expectTypeOf<ReplyPreview["id"]>().toEqualTypeOf<string>();
|
|
59
|
+
expectTypeOf<ReplyPreview["nick_name"]>().toEqualTypeOf<string>();
|
|
60
|
+
expectTypeOf<ReplyPreview["fragments"]>().toEqualTypeOf<Fragment[]>();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("uses local fragments for message bodies and flat stream completion payloads", () => {
|
|
64
|
+
expectTypeOf<Fragment>().toMatchTypeOf<ProtocolFragment>();
|
|
65
|
+
expectTypeOf<MessagePayload["message"]["body"]["fragments"]>().toEqualTypeOf<Fragment[]>();
|
|
66
|
+
expectTypeOf<StreamDonePayload["fragments"]>().toEqualTypeOf<Fragment[]>();
|
|
67
|
+
expectTypeOf<StreamDonePayload>().not.toHaveProperty("message");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
export const EVENT = {
|
|
2
|
+
CONNECT_CHALLENGE: "connect.challenge",
|
|
3
|
+
CONNECT: "connect",
|
|
4
|
+
HELLO_OK: "hello-ok",
|
|
5
|
+
HELLO_FAIL: "hello-fail",
|
|
6
|
+
MESSAGE_SEND: "message.send",
|
|
7
|
+
MESSAGE_ACK: "message.ack",
|
|
8
|
+
MESSAGE_ERROR: "message.error",
|
|
9
|
+
MESSAGE_REPLY: "message.reply",
|
|
10
|
+
MESSAGE_CREATED: "message.created",
|
|
11
|
+
MESSAGE_ADD: "message.add",
|
|
12
|
+
MESSAGE_DONE: "message.done",
|
|
13
|
+
MESSAGE_FAILED: "message.failed",
|
|
14
|
+
TYPING_UPDATE: "typing.update",
|
|
15
|
+
CHAT_METADATA_INVALIDATED: "chat.metadata.invalidated",
|
|
16
|
+
OFFLINE_BATCH: "offline.batch",
|
|
17
|
+
OFFLINE_ACK: "offline.ack",
|
|
18
|
+
OFFLINE_DONE: "offline.done",
|
|
19
|
+
PING: "ping",
|
|
20
|
+
PONG: "pong",
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
export type KnownEventName = typeof EVENT[keyof typeof EVENT];
|
|
24
|
+
export type EventName = KnownEventName | string;
|
|
25
|
+
export type ChatType = "direct" | "group";
|
|
26
|
+
export type ConnState =
|
|
27
|
+
| "idle"
|
|
28
|
+
| "connecting"
|
|
29
|
+
| "challenging"
|
|
30
|
+
| "authenticating"
|
|
31
|
+
| "connected"
|
|
32
|
+
| "reconnecting"
|
|
33
|
+
| "disconnected";
|
|
34
|
+
export type TransportState = "closed" | "connecting" | "open";
|
|
35
|
+
|
|
36
|
+
export interface TransportEvents {
|
|
37
|
+
onOpen: () => void;
|
|
38
|
+
onMessage: (data: string | Buffer) => void;
|
|
39
|
+
onClose: (code: number, reason: string) => void;
|
|
40
|
+
onError: (err: Error) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface Transport {
|
|
44
|
+
readonly state: TransportState;
|
|
45
|
+
connect(url: string, handlers: TransportEvents): Promise<void>;
|
|
46
|
+
send(data: string): void;
|
|
47
|
+
close(code?: number, reason?: string): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Routing {
|
|
51
|
+
id: string;
|
|
52
|
+
type: ChatType;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface Sender {
|
|
56
|
+
id: string;
|
|
57
|
+
type: "direct";
|
|
58
|
+
nick_name: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Envelope<TPayload = unknown> {
|
|
62
|
+
version: "2";
|
|
63
|
+
event: EventName;
|
|
64
|
+
trace_id: string;
|
|
65
|
+
emitted_at: number;
|
|
66
|
+
chat_id?: string;
|
|
67
|
+
chat_type?: ChatType;
|
|
68
|
+
to?: Routing;
|
|
69
|
+
sender?: Sender;
|
|
70
|
+
payload: TPayload;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ChallengePayload {
|
|
74
|
+
nonce: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ConnectCapabilities {
|
|
78
|
+
multi_device?: boolean;
|
|
79
|
+
device_replay?: boolean;
|
|
80
|
+
chat_meta_events?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ConnectPayload {
|
|
84
|
+
token: string;
|
|
85
|
+
nonce: string;
|
|
86
|
+
device_id?: string;
|
|
87
|
+
capabilities?: ConnectCapabilities;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface HelloOKPayload {
|
|
91
|
+
device_id?: string;
|
|
92
|
+
delivery_mode?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface HelloFailPayload {
|
|
96
|
+
reason: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface TextFragment {
|
|
100
|
+
kind: "text";
|
|
101
|
+
text: string;
|
|
102
|
+
delta?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface MentionFragment {
|
|
106
|
+
kind: "mention";
|
|
107
|
+
user_id: string;
|
|
108
|
+
display?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface ImageFragment {
|
|
112
|
+
kind: "image";
|
|
113
|
+
url: string;
|
|
114
|
+
name?: string;
|
|
115
|
+
mime?: string;
|
|
116
|
+
size?: number;
|
|
117
|
+
width?: number;
|
|
118
|
+
height?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface FileFragment {
|
|
122
|
+
kind: "file";
|
|
123
|
+
url: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
mime?: string;
|
|
126
|
+
size?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface AudioFragment {
|
|
130
|
+
kind: "audio";
|
|
131
|
+
url: string;
|
|
132
|
+
name?: string;
|
|
133
|
+
mime?: string;
|
|
134
|
+
size?: number;
|
|
135
|
+
duration?: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface VideoFragment {
|
|
139
|
+
kind: "video";
|
|
140
|
+
url: string;
|
|
141
|
+
name?: string;
|
|
142
|
+
mime?: string;
|
|
143
|
+
size?: number;
|
|
144
|
+
width?: number;
|
|
145
|
+
height?: number;
|
|
146
|
+
duration?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type Fragment =
|
|
150
|
+
| TextFragment
|
|
151
|
+
| MentionFragment
|
|
152
|
+
| ImageFragment
|
|
153
|
+
| FileFragment
|
|
154
|
+
| AudioFragment
|
|
155
|
+
| VideoFragment;
|
|
156
|
+
|
|
157
|
+
export interface UnknownFragment {
|
|
158
|
+
kind: string;
|
|
159
|
+
[field: string]: unknown;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type ProtocolFragment = Fragment | UnknownFragment;
|
|
163
|
+
|
|
164
|
+
export interface Streaming {
|
|
165
|
+
status: "static" | "streaming" | "done" | "failed" | (string & {});
|
|
166
|
+
sequence: number;
|
|
167
|
+
mutation_policy: "sealed" | "append_text_only" | (string & {});
|
|
168
|
+
started_at?: number | null;
|
|
169
|
+
completed_at?: number | null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface MessageContext {
|
|
173
|
+
mentions: unknown[];
|
|
174
|
+
reply: {
|
|
175
|
+
reply_to_msg_id: string;
|
|
176
|
+
reply_preview: {
|
|
177
|
+
id: string;
|
|
178
|
+
nick_name: string;
|
|
179
|
+
fragments: Fragment[];
|
|
180
|
+
} | null;
|
|
181
|
+
} | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface MessagePayload {
|
|
185
|
+
message_id?: string;
|
|
186
|
+
message_mode: string;
|
|
187
|
+
message: {
|
|
188
|
+
body: { fragments: Fragment[] };
|
|
189
|
+
context: MessageContext;
|
|
190
|
+
streaming?: Streaming;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface MessageAckPayload {
|
|
195
|
+
message_id: string;
|
|
196
|
+
accepted_at: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface MessageErrorPayload {
|
|
200
|
+
code: string;
|
|
201
|
+
message: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface ChatMetadataInvalidatedPayload {
|
|
205
|
+
scope?: string[];
|
|
206
|
+
version?: number;
|
|
207
|
+
updated_at?: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface StreamCreatedPayload {
|
|
211
|
+
message_id: string;
|
|
212
|
+
message_mode?: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface Mutation {
|
|
216
|
+
type: string;
|
|
217
|
+
target_fragment_index: number | null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface StreamAddPayload {
|
|
221
|
+
message_id: string;
|
|
222
|
+
sequence: number;
|
|
223
|
+
mutation?: Mutation | null;
|
|
224
|
+
fragments: Fragment[];
|
|
225
|
+
streaming?: Streaming;
|
|
226
|
+
added_at?: number;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface StreamDonePayload {
|
|
230
|
+
message_id: string;
|
|
231
|
+
fragments: Fragment[];
|
|
232
|
+
streaming?: Streaming;
|
|
233
|
+
completed_at?: number;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export type StreamFailedPayload = StreamDonePayload;
|
|
237
|
+
|
|
238
|
+
export interface OfflineBatchPayload {
|
|
239
|
+
batch_id: number;
|
|
240
|
+
items: Envelope[];
|
|
241
|
+
remaining: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface OfflineAckPayload {
|
|
245
|
+
batch_id: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export type OfflineDonePayload = Record<string, never>;
|
|
249
|
+
|
|
250
|
+
export type EmptyPayload = Record<string, never>;
|
|
251
|
+
|
|
252
|
+
export interface TypingUpdatePayload {
|
|
253
|
+
is_typing: boolean;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export class AuthError extends Error {
|
|
257
|
+
override name = "AuthError";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export class TransportError extends Error {
|
|
261
|
+
override name = "TransportError";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export class ProtocolError extends Error {
|
|
265
|
+
override name = "ProtocolError";
|
|
266
|
+
constructor(message: string, readonly envelope?: unknown) {
|
|
267
|
+
super(message);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export class AckTimeoutError extends Error {
|
|
272
|
+
override name = "AckTimeoutError";
|
|
273
|
+
constructor(readonly traceId: string, readonly timeoutMs: number) {
|
|
274
|
+
super(`ack timeout after ${timeoutMs}ms for trace_id=${traceId}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export class MessageSendError extends Error {
|
|
279
|
+
override name = "MessageSendError";
|
|
280
|
+
constructor(
|
|
281
|
+
readonly traceId: string,
|
|
282
|
+
readonly code: string,
|
|
283
|
+
message: string,
|
|
284
|
+
readonly chatId?: string,
|
|
285
|
+
) {
|
|
286
|
+
super(`message.error ${code}: ${message} for trace_id=${traceId}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export class StateError extends Error {
|
|
291
|
+
override name = "StateError";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function isBusinessDispatchEvent(event: string): boolean {
|
|
295
|
+
return event === EVENT.MESSAGE_SEND || event === EVENT.MESSAGE_REPLY || event === EVENT.MESSAGE_DONE;
|
|
296
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConnectPayload,
|
|
3
|
+
EmptyPayload,
|
|
4
|
+
Envelope,
|
|
5
|
+
Fragment,
|
|
6
|
+
MessagePayload,
|
|
7
|
+
OfflineBatchPayload,
|
|
8
|
+
ProtocolFragment,
|
|
9
|
+
Sender,
|
|
10
|
+
StreamAddPayload,
|
|
11
|
+
StreamDonePayload,
|
|
12
|
+
} from "./protocol-types.ts";
|
|
13
|
+
|
|
14
|
+
type Assert<T extends true> = T;
|
|
15
|
+
type IsAssignable<From, To> = [From] extends [To] ? true : false;
|
|
16
|
+
type IsEqual<Actual, Expected> =
|
|
17
|
+
(<T>() => T extends Actual ? 1 : 2) extends <T>() => T extends Expected ? 1 : 2
|
|
18
|
+
? true
|
|
19
|
+
: false;
|
|
20
|
+
type Not<T extends boolean> = T extends true ? false : true;
|
|
21
|
+
|
|
22
|
+
type _SenderRequiresType = Assert<
|
|
23
|
+
Not<IsAssignable<{ id: string; nick_name: string }, Sender>>
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
type _MaterializedMessageIdMayBeOmittedOnUplink = Assert<
|
|
27
|
+
IsAssignable<
|
|
28
|
+
{ message_mode: string; message: MessagePayload["message"] },
|
|
29
|
+
MessagePayload
|
|
30
|
+
>
|
|
31
|
+
>;
|
|
32
|
+
|
|
33
|
+
type _KnownTextFragmentRequiresText = Assert<
|
|
34
|
+
Not<IsAssignable<{ kind: "text" }, Fragment>>
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
type _KnownMentionFragmentRequiresUserId = Assert<
|
|
38
|
+
Not<IsAssignable<{ kind: "mention" }, Fragment>>
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
type _UnknownWireFragmentsAreRepresentable = Assert<
|
|
42
|
+
IsAssignable<{ kind: "sticker"; sticker_id: string }, ProtocolFragment>
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
type _MaterializedMessageBodyUsesWireFragments = Assert<
|
|
46
|
+
IsEqual<MessagePayload["message"]["body"]["fragments"], Fragment[]>
|
|
47
|
+
>;
|
|
48
|
+
|
|
49
|
+
type _MaterializedMessageRejectsMalformedKnownFragments = Assert<
|
|
50
|
+
Not<
|
|
51
|
+
IsAssignable<
|
|
52
|
+
{
|
|
53
|
+
message_mode: string;
|
|
54
|
+
message: {
|
|
55
|
+
body: { fragments: [{ kind: "text" }] };
|
|
56
|
+
context: MessagePayload["message"]["context"];
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
MessagePayload
|
|
60
|
+
>
|
|
61
|
+
>
|
|
62
|
+
>;
|
|
63
|
+
|
|
64
|
+
type _StreamCompletionUsesWireFragments = Assert<
|
|
65
|
+
IsEqual<StreamDonePayload["fragments"], Fragment[]>
|
|
66
|
+
>;
|
|
67
|
+
|
|
68
|
+
type _StreamCompletionPayloadIsFlat = Assert<
|
|
69
|
+
Not<"message" extends keyof StreamDonePayload ? true : false>
|
|
70
|
+
>;
|
|
71
|
+
|
|
72
|
+
type _ConnectPayloadAllowsCanonicalMinimum = Assert<
|
|
73
|
+
IsAssignable<{ token: string; nonce: string }, ConnectPayload>
|
|
74
|
+
>;
|
|
75
|
+
|
|
76
|
+
type _StreamAddPayloadAllowsCanonicalMinimum = Assert<
|
|
77
|
+
IsAssignable<
|
|
78
|
+
{ message_id: string; sequence: number; fragments: Fragment[] },
|
|
79
|
+
StreamAddPayload
|
|
80
|
+
>
|
|
81
|
+
>;
|
|
82
|
+
|
|
83
|
+
type _OfflineBatchPayloadCarriesEnvelopes = Assert<
|
|
84
|
+
IsAssignable<{ batch_id: number; items: Envelope[]; remaining: number }, OfflineBatchPayload>
|
|
85
|
+
>;
|
|
86
|
+
|
|
87
|
+
type _ControlPayloadsAreEmptyObjects = Assert<
|
|
88
|
+
IsAssignable<Record<string, never>, EmptyPayload>
|
|
89
|
+
>;
|
package/src/protocol.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isInboundMessagePayload,
|
|
2
|
+
import { isInboundMessagePayload, hasRenderableText } from "./protocol.ts";
|
|
3
3
|
|
|
4
4
|
describe("openclaw-clawchat protocol guards", () => {
|
|
5
5
|
it("accepts a well-formed downlink message payload", () => {
|
|
@@ -29,11 +29,6 @@ describe("openclaw-clawchat protocol guards", () => {
|
|
|
29
29
|
expect(isInboundMessagePayload({ message_id: "m1" })).toBe(false);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it("detects group sender", () => {
|
|
33
|
-
expect(isGroupSender({ sender_id: "g", type: "group", display_name: "Group" })).toBe(true);
|
|
34
|
-
expect(isGroupSender({ sender_id: "u", type: "direct", display_name: "User" })).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
32
|
it("detects renderable text", () => {
|
|
38
33
|
expect(hasRenderableText({ body: { fragments: [{ kind: "text", text: "hi" }] } })).toBe(true);
|
|
39
34
|
expect(hasRenderableText({ body: { fragments: [{ kind: "image", url: "x" }] } })).toBe(true);
|
package/src/protocol.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local narrow guards for inbound protocol envelopes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* payload to `
|
|
4
|
+
* Raw client events hand us `Envelope<unknown>`. Before casting the
|
|
5
|
+
* payload to `MessagePayload` we run these cheap structural checks
|
|
6
6
|
* so runtime errors surface as skipped messages, not crashes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -40,8 +40,3 @@ export function hasRenderableText(message: {
|
|
|
40
40
|
(f as { url: string }).url.trim().length > 0)),
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
export function isGroupSender(sender: unknown): boolean {
|
|
45
|
-
if (!sender || typeof sender !== "object") return false;
|
|
46
|
-
return (sender as { type?: unknown }).type === "group";
|
|
47
|
-
}
|