@clawling/clawchat-plugin-openclaw 2026.5.12-28

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.
Files changed (114) hide show
  1. package/INSTALL.md +64 -0
  2. package/README.md +227 -0
  3. package/dist/index.js +20 -0
  4. package/dist/setup-entry.js +3 -0
  5. package/dist/src/api-client.js +263 -0
  6. package/dist/src/api-types.js +17 -0
  7. package/dist/src/api-types.test-d.js +10 -0
  8. package/dist/src/buffered-stream.js +177 -0
  9. package/dist/src/channel.js +66 -0
  10. package/dist/src/channel.setup.js +119 -0
  11. package/dist/src/clawchat-memory.js +403 -0
  12. package/dist/src/clawchat-metadata.js +310 -0
  13. package/dist/src/client.js +35 -0
  14. package/dist/src/commands.js +35 -0
  15. package/dist/src/config.js +274 -0
  16. package/dist/src/group-message-coalescer.js +119 -0
  17. package/dist/src/inbound.js +170 -0
  18. package/dist/src/llm-context-debug.js +86 -0
  19. package/dist/src/login.runtime.js +204 -0
  20. package/dist/src/media-runtime.js +85 -0
  21. package/dist/src/message-mapper.js +146 -0
  22. package/dist/src/mock-transport.js +31 -0
  23. package/dist/src/outbound.js +628 -0
  24. package/dist/src/plugin-prompts.js +89 -0
  25. package/dist/src/profile-prompt.js +269 -0
  26. package/dist/src/profile-sync.js +110 -0
  27. package/dist/src/prompt-injection.js +25 -0
  28. package/dist/src/protocol-types.js +63 -0
  29. package/dist/src/protocol-types.typecheck.js +1 -0
  30. package/dist/src/protocol.js +33 -0
  31. package/dist/src/reply-dispatcher.js +422 -0
  32. package/dist/src/runtime.js +1254 -0
  33. package/dist/src/storage.js +525 -0
  34. package/dist/src/streaming.js +65 -0
  35. package/dist/src/terminal-send.js +36 -0
  36. package/dist/src/tools-schema.js +208 -0
  37. package/dist/src/tools.js +920 -0
  38. package/dist/src/ws-alignment.js +178 -0
  39. package/dist/src/ws-client.js +588 -0
  40. package/dist/src/ws-log.js +19 -0
  41. package/index.ts +24 -0
  42. package/openclaw.plugin.json +169 -0
  43. package/package.json +80 -0
  44. package/prompts/default-group-bio.md +19 -0
  45. package/prompts/default-owner-behavior.md +27 -0
  46. package/prompts/platform.md +13 -0
  47. package/setup-entry.ts +4 -0
  48. package/skills/clawchat/SKILL.md +91 -0
  49. package/src/api-client.test.ts +827 -0
  50. package/src/api-client.ts +414 -0
  51. package/src/api-types.ts +146 -0
  52. package/src/channel.outbound.test.ts +433 -0
  53. package/src/channel.setup.ts +145 -0
  54. package/src/channel.test.ts +262 -0
  55. package/src/channel.ts +81 -0
  56. package/src/clawchat-memory.test.ts +480 -0
  57. package/src/clawchat-memory.ts +533 -0
  58. package/src/clawchat-metadata.test.ts +477 -0
  59. package/src/clawchat-metadata.ts +429 -0
  60. package/src/client.test.ts +169 -0
  61. package/src/client.ts +56 -0
  62. package/src/commands.test.ts +39 -0
  63. package/src/commands.ts +41 -0
  64. package/src/config.test.ts +344 -0
  65. package/src/config.ts +404 -0
  66. package/src/group-message-coalescer.test.ts +237 -0
  67. package/src/group-message-coalescer.ts +171 -0
  68. package/src/inbound.test.ts +508 -0
  69. package/src/inbound.ts +278 -0
  70. package/src/llm-context-debug.test.ts +55 -0
  71. package/src/llm-context-debug.ts +139 -0
  72. package/src/login.runtime.test.ts +737 -0
  73. package/src/login.runtime.ts +277 -0
  74. package/src/manifest.test.ts +352 -0
  75. package/src/media-runtime.test.ts +207 -0
  76. package/src/media-runtime.ts +152 -0
  77. package/src/message-mapper.test.ts +201 -0
  78. package/src/message-mapper.ts +174 -0
  79. package/src/mock-transport.test.ts +35 -0
  80. package/src/mock-transport.ts +38 -0
  81. package/src/outbound.test.ts +1269 -0
  82. package/src/outbound.ts +803 -0
  83. package/src/plugin-entry.test.ts +38 -0
  84. package/src/plugin-prompts.test.ts +94 -0
  85. package/src/plugin-prompts.ts +107 -0
  86. package/src/profile-prompt.test.ts +274 -0
  87. package/src/profile-prompt.ts +351 -0
  88. package/src/profile-sync.test.ts +539 -0
  89. package/src/profile-sync.ts +191 -0
  90. package/src/prompt-injection.test.ts +39 -0
  91. package/src/prompt-injection.ts +45 -0
  92. package/src/protocol-types.test.ts +69 -0
  93. package/src/protocol-types.ts +296 -0
  94. package/src/protocol-types.typecheck.ts +89 -0
  95. package/src/protocol.test.ts +39 -0
  96. package/src/protocol.ts +42 -0
  97. package/src/reply-dispatcher.test.ts +1324 -0
  98. package/src/reply-dispatcher.ts +555 -0
  99. package/src/runtime.test.ts +4719 -0
  100. package/src/runtime.ts +1493 -0
  101. package/src/scripts.test.ts +85 -0
  102. package/src/storage.test.ts +560 -0
  103. package/src/storage.ts +807 -0
  104. package/src/terminal-send.test.ts +81 -0
  105. package/src/terminal-send.ts +56 -0
  106. package/src/tools-schema.ts +337 -0
  107. package/src/tools.test.ts +933 -0
  108. package/src/tools.ts +1185 -0
  109. package/src/ws-alignment.test.ts +103 -0
  110. package/src/ws-alignment.ts +275 -0
  111. package/src/ws-client.test.ts +1217 -0
  112. package/src/ws-client.ts +662 -0
  113. package/src/ws-log.test.ts +32 -0
  114. package/src/ws-log.ts +31 -0
@@ -0,0 +1,191 @@
1
+ import type { ConversationDetails, Profile } from "./api-types.ts";
2
+ import {
3
+ pullGroupMetadata,
4
+ pullOwnerMetadata,
5
+ pullUserMetadata,
6
+ } from "./clawchat-metadata.ts";
7
+
8
+ type ProfileSyncApi = {
9
+ getConversation?: (conversationId: string) => Promise<{ conversation: ConversationDetails }>;
10
+ getUserInfo?: (userId: string) => Promise<Profile>;
11
+ getUserProfile?: (userId: string) => Promise<Profile>;
12
+ getAgentDetail?: (agentId: string) => Promise<{ agent: unknown }>;
13
+ getAgentProfile?: (agentId: string) => Promise<{ agent: unknown }>;
14
+ };
15
+
16
+ type ProfileSyncStore = {
17
+ readonly __conversationCacheRemoved?: true;
18
+ };
19
+
20
+ function requireMemoryRoot(
21
+ memoryRoot: string | undefined,
22
+ log: { error?: (message: string) => void } | undefined,
23
+ label: string,
24
+ ): memoryRoot is string {
25
+ if (typeof memoryRoot === "string" && memoryRoot.trim()) return true;
26
+ log?.error?.(`clawchat-plugin-openclaw ${label} metadata refresh requires memoryRoot`);
27
+ return false;
28
+ }
29
+
30
+ export async function ensureUserProfileForSender(params: {
31
+ platform: string;
32
+ accountId: string;
33
+ accountUserId: string;
34
+ accountOwnerUserId: string;
35
+ sender: { id: string; nickname?: string | null };
36
+ lastSeenAt?: number | null;
37
+ api: ProfileSyncApi;
38
+ store: ProfileSyncStore;
39
+ memoryRoot?: string;
40
+ log?: { error?: (message: string) => void };
41
+ }): Promise<void> {
42
+ void params.accountUserId;
43
+ void params.accountOwnerUserId;
44
+ void params.lastSeenAt;
45
+ void params.sender.nickname;
46
+ if (!requireMemoryRoot(params.memoryRoot, params.log, "user")) return;
47
+ try {
48
+ await pullUserMetadata({
49
+ memoryRoot: params.memoryRoot,
50
+ userId: params.sender.id,
51
+ api: params.api,
52
+ });
53
+ } catch (err) {
54
+ params.log?.error?.(
55
+ `clawchat-plugin-openclaw first-seen user metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`,
56
+ );
57
+ }
58
+ }
59
+
60
+ export async function refreshGroupProfile(params: {
61
+ platform: string;
62
+ accountId: string;
63
+ accountUserId?: string;
64
+ accountOwnerUserId?: string;
65
+ chatId?: string;
66
+ conversationId?: string;
67
+ metadataVersion?: number;
68
+ api: ProfileSyncApi;
69
+ store: ProfileSyncStore;
70
+ memoryRoot?: string;
71
+ log?: { error?: (message: string) => void };
72
+ }): Promise<boolean> {
73
+ const conversationId = params.conversationId ?? params.chatId ?? "";
74
+ if (!conversationId || !params.api.getConversation) return false;
75
+ try {
76
+ if (requireMemoryRoot(params.memoryRoot, params.log, "group")) {
77
+ const result = await pullGroupMetadata({
78
+ memoryRoot: params.memoryRoot,
79
+ groupId: conversationId,
80
+ api: params.api,
81
+ skipUserIds: [params.accountUserId, params.accountOwnerUserId],
82
+ });
83
+ if (result.failures.length > 0) {
84
+ params.log?.error?.(
85
+ `clawchat-plugin-openclaw group participant metadata refresh partially failed: ${result.failures.map((failure) => `${failure.targetId}: ${failure.error}`).join("; ")}`,
86
+ );
87
+ }
88
+ if (!result.conversation) return false;
89
+ } else {
90
+ await params.api.getConversation(conversationId);
91
+ }
92
+ return true;
93
+ } catch (err) {
94
+ params.log?.error?.(
95
+ `clawchat-plugin-openclaw group metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`,
96
+ );
97
+ return false;
98
+ }
99
+ }
100
+
101
+ export async function ensureGroupProfileForChat(params: {
102
+ platform: string;
103
+ accountId: string;
104
+ accountUserId?: string;
105
+ accountOwnerUserId?: string;
106
+ chat: { id: string; type: "direct" | "group"; lastSeenAt?: number | null };
107
+ api: ProfileSyncApi;
108
+ store: ProfileSyncStore;
109
+ memoryRoot?: string;
110
+ log?: { error?: (message: string) => void };
111
+ }): Promise<boolean> {
112
+ if (params.chat.type !== "group") return false;
113
+ return await refreshGroupProfile({
114
+ platform: params.platform,
115
+ accountId: params.accountId,
116
+ accountUserId: params.accountUserId,
117
+ accountOwnerUserId: params.accountOwnerUserId,
118
+ conversationId: params.chat.id,
119
+ api: params.api,
120
+ store: params.store,
121
+ memoryRoot: params.memoryRoot,
122
+ log: params.log,
123
+ });
124
+ }
125
+
126
+ export async function refreshAgentBehaviorProfile(params: {
127
+ platform: string;
128
+ accountId: string;
129
+ accountUserId: string;
130
+ accountOwnerUserId: string;
131
+ agentId?: string;
132
+ metadataVersion?: number;
133
+ api: ProfileSyncApi;
134
+ store: ProfileSyncStore;
135
+ memoryRoot?: string;
136
+ log?: { error?: (message: string) => void };
137
+ }): Promise<void> {
138
+ if (!requireMemoryRoot(params.memoryRoot, params.log, "owner")) return;
139
+ try {
140
+ await pullOwnerMetadata({
141
+ memoryRoot: params.memoryRoot,
142
+ agentId: params.agentId ?? "",
143
+ accountUserId: params.accountUserId,
144
+ accountOwnerUserId: params.accountOwnerUserId,
145
+ api: params.api,
146
+ });
147
+ } catch (err) {
148
+ params.log?.error?.(
149
+ `clawchat-plugin-openclaw owner metadata refresh failed: ${err instanceof Error ? err.message : String(err)}`,
150
+ );
151
+ }
152
+ }
153
+
154
+ export async function syncFirstSeenClawChatProfiles(params: {
155
+ platform: string;
156
+ accountId: string;
157
+ accountUserId: string;
158
+ accountOwnerUserId: string;
159
+ chat: { id: string; type: "direct" | "group"; lastSeenAt?: number | null };
160
+ sender: { id: string; nickname?: string | null };
161
+ api: ProfileSyncApi;
162
+ store: ProfileSyncStore;
163
+ memoryRoot?: string;
164
+ log?: { error?: (message: string) => void };
165
+ }): Promise<void> {
166
+ const { platform, accountId, accountUserId, accountOwnerUserId, chat, sender, api, store } = params;
167
+ await ensureGroupProfileForChat({
168
+ platform,
169
+ accountId,
170
+ chat,
171
+ accountUserId,
172
+ accountOwnerUserId,
173
+ api,
174
+ store,
175
+ memoryRoot: params.memoryRoot,
176
+ log: params.log,
177
+ });
178
+ await ensureUserProfileForSender({
179
+ platform,
180
+ accountId,
181
+ accountUserId,
182
+ accountOwnerUserId,
183
+ sender,
184
+ lastSeenAt: chat.lastSeenAt ?? null,
185
+ api,
186
+ store,
187
+ memoryRoot: params.memoryRoot,
188
+ log: params.log,
189
+ });
190
+
191
+ }
@@ -0,0 +1,39 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ clearClawChatPromptInjections,
4
+ clearClawChatPromptInjectionForSession,
5
+ registerClawChatPromptInjection,
6
+ renderClawChatPromptInjectionForSession,
7
+ stageClawChatPromptInjection,
8
+ } from "./prompt-injection.ts";
9
+
10
+ const DIRECT_PROMPT = "\n[message]\n\nsender_is_agent_owner:";
11
+
12
+ describe("ClawChat prompt injection", () => {
13
+ beforeEach(() => clearClawChatPromptInjections());
14
+
15
+ it("renders a staged user prompt by session key", () => {
16
+ stageClawChatPromptInjection({ sessionKey: "session-1", prompt: DIRECT_PROMPT });
17
+ expect(renderClawChatPromptInjectionForSession("session-1")).toBe(DIRECT_PROMPT);
18
+ });
19
+
20
+ it("clears a staged prompt by session key", () => {
21
+ stageClawChatPromptInjection({ sessionKey: "session-1", prompt: DIRECT_PROMPT });
22
+ clearClawChatPromptInjectionForSession("session-1");
23
+ expect(renderClawChatPromptInjectionForSession("session-1")).toBeUndefined();
24
+ });
25
+
26
+ it("consumes staged prompt through before_prompt_build appendSystemContext", async () => {
27
+ const handlers = new Map<string, Function>();
28
+ const api = {
29
+ on: vi.fn((name: string, handler: Function) => handlers.set(name, handler)),
30
+ logger: { info: vi.fn(), error: vi.fn() },
31
+ };
32
+ registerClawChatPromptInjection(api as never);
33
+ expect(api.on).toHaveBeenCalledWith("before_prompt_build", expect.any(Function), { priority: 100 });
34
+ stageClawChatPromptInjection({ sessionKey: "session-1", prompt: DIRECT_PROMPT });
35
+ const result = await handlers.get("before_prompt_build")?.({}, { sessionKey: "session-1" });
36
+ expect(result).toEqual({ appendSystemContext: DIRECT_PROMPT });
37
+ expect(renderClawChatPromptInjectionForSession("session-1")).toBeUndefined();
38
+ });
39
+ });
@@ -0,0 +1,45 @@
1
+ export type ClawChatPromptInjectionApi = {
2
+ on: (
3
+ name: "before_prompt_build",
4
+ handler: (
5
+ event: unknown,
6
+ ctx: { sessionKey?: string },
7
+ ) => Promise<{ appendSystemContext: string } | undefined>,
8
+ options?: { priority?: number },
9
+ ) => void;
10
+ };
11
+
12
+ const promptInjectionsBySessionKey = new Map<string, string>();
13
+
14
+ export function clearClawChatPromptInjections(): void {
15
+ promptInjectionsBySessionKey.clear();
16
+ }
17
+
18
+ export function clearClawChatPromptInjectionForSession(sessionKey: string): void {
19
+ promptInjectionsBySessionKey.delete(sessionKey);
20
+ }
21
+
22
+ export function stageClawChatPromptInjection(params: { sessionKey: string; prompt: string }): void {
23
+ promptInjectionsBySessionKey.set(params.sessionKey, params.prompt);
24
+ }
25
+
26
+ export function renderClawChatPromptInjectionForSession(sessionKey: string): string | undefined {
27
+ return promptInjectionsBySessionKey.get(sessionKey);
28
+ }
29
+
30
+ export function registerClawChatPromptInjection(api: ClawChatPromptInjectionApi): void {
31
+ api.on(
32
+ "before_prompt_build",
33
+ async (_event, ctx) => {
34
+ const sessionKey = ctx.sessionKey;
35
+ if (!sessionKey) return undefined;
36
+
37
+ const prompt = promptInjectionsBySessionKey.get(sessionKey);
38
+ if (!prompt) return undefined;
39
+
40
+ promptInjectionsBySessionKey.delete(sessionKey);
41
+ return { appendSystemContext: prompt };
42
+ },
43
+ { priority: 100 },
44
+ );
45
+ }
@@ -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 complete materialized messages 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(false);
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;
296
+ }