@newbase-clawchat/openclaw-clawchat 2026.4.29 → 2026.4.30

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 (47) hide show
  1. package/README.md +33 -10
  2. package/dist/index.js +27 -0
  3. package/dist/src/api-client.js +156 -0
  4. package/dist/src/api-types.js +17 -0
  5. package/dist/src/buffered-stream.js +177 -0
  6. package/dist/src/channel.js +191 -0
  7. package/dist/src/client.js +176 -0
  8. package/dist/src/commands.js +35 -0
  9. package/dist/src/config.js +214 -0
  10. package/dist/src/inbound.js +133 -0
  11. package/dist/src/login.runtime.js +130 -0
  12. package/dist/src/media-runtime.js +85 -0
  13. package/dist/src/message-mapper.js +82 -0
  14. package/dist/src/outbound.js +181 -0
  15. package/dist/src/protocol.js +38 -0
  16. package/dist/src/reply-dispatcher.js +440 -0
  17. package/dist/src/runtime.js +288 -0
  18. package/dist/src/streaming.js +65 -0
  19. package/dist/src/tools-schema.js +38 -0
  20. package/dist/src/tools.js +287 -0
  21. package/openclaw.plugin.json +12 -0
  22. package/package.json +25 -5
  23. package/skills/clawchat-activate/SKILL.md +17 -8
  24. package/src/buffered-stream.test.ts +10 -0
  25. package/src/buffered-stream.ts +6 -6
  26. package/src/channel.outbound.test.ts +3 -3
  27. package/src/channel.ts +11 -3
  28. package/src/client.test.ts +8 -1
  29. package/src/client.ts +11 -10
  30. package/src/commands.test.ts +6 -0
  31. package/src/commands.ts +5 -1
  32. package/src/config.test.ts +3 -0
  33. package/src/config.ts +7 -0
  34. package/src/inbound.test.ts +4 -1
  35. package/src/inbound.ts +11 -10
  36. package/src/login.runtime.test.ts +36 -0
  37. package/src/login.runtime.ts +54 -26
  38. package/src/manifest.test.ts +98 -22
  39. package/src/outbound.test.ts +6 -5
  40. package/src/outbound.ts +8 -7
  41. package/src/reply-dispatcher.test.ts +418 -3
  42. package/src/reply-dispatcher.ts +137 -12
  43. package/src/runtime.ts +1 -0
  44. package/src/streaming.test.ts +12 -9
  45. package/src/streaming.ts +6 -6
  46. package/src/tools.test.ts +81 -18
  47. package/src/tools.ts +63 -72
@@ -1,8 +1,7 @@
1
1
  import { createInterface, type Interface as ReadlineInterface } from "node:readline/promises";
2
- import { writeConfigFile } from "openclaw/plugin-sdk/config-runtime";
3
2
  import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
4
3
  import { createOpenclawClawlingApiClient } from "./api-client.ts";
5
- import { ClawlingApiError } from "./api-types.ts";
4
+ import { ClawlingApiError, type AgentConnectResult } from "./api-types.ts";
6
5
  import {
7
6
  CHANNEL_ID,
8
7
  mergeOpenclawClawchatToolAllow,
@@ -20,6 +19,14 @@ export const AGENTS_CONNECT_PLATFORM = "openclaw" as const;
20
19
  */
21
20
  export const AGENTS_CONNECT_TYPE = "clawbot" as const;
22
21
 
22
+ export type OpenclawClawchatMutateConfigFile = <T = void>(params: {
23
+ afterWrite: { mode: "auto" } | { mode: "none" | "restart"; reason: string };
24
+ mutate: (
25
+ draft: OpenClawConfig,
26
+ context: { snapshot: unknown; previousHash: string | null },
27
+ ) => Promise<T | void> | T | void;
28
+ }) => Promise<unknown>;
29
+
23
30
  export interface LoginParams {
24
31
  cfg: OpenClawConfig;
25
32
  accountId?: string | null;
@@ -31,7 +38,9 @@ export interface LoginParams {
31
38
  readInviteCode?: () => Promise<string>;
32
39
  /** Override for the HTTP client — used by tests. */
33
40
  apiClientFactory?: typeof createOpenclawClawlingApiClient;
34
- /** Override for config persistence used by tests. */
41
+ /** Official runtime config mutator. Production callers must provide this. */
42
+ mutateConfigFile?: OpenclawClawchatMutateConfigFile;
43
+ /** Test-only config persistence override. */
35
44
  persistConfig?: (cfg: OpenClawConfig) => Promise<void> | void;
36
45
  }
37
46
 
@@ -60,6 +69,46 @@ async function promptInviteCodeFromStdin(runtime: {
60
69
  }
61
70
  }
62
71
 
72
+ function buildLoginConfig(cfg: OpenClawConfig, result: AgentConnectResult): OpenClawConfig {
73
+ const channels = (cfg.channels ?? {}) as Record<string, unknown>;
74
+ const existing = (channels[CHANNEL_ID] ?? {}) as Record<string, unknown>;
75
+ const nextSection: Record<string, unknown> = {
76
+ ...existing,
77
+ enabled: true,
78
+ token: result.access_token,
79
+ userId: result.agent.user_id,
80
+ };
81
+ if (result.refresh_token) {
82
+ nextSection.refreshToken = result.refresh_token;
83
+ }
84
+ return mergeOpenclawClawchatToolAllow({
85
+ ...cfg,
86
+ channels: { ...channels, [CHANNEL_ID]: nextSection },
87
+ });
88
+ }
89
+
90
+ async function persistLoginConfig(
91
+ params: LoginParams,
92
+ result: AgentConnectResult,
93
+ ): Promise<void> {
94
+ if (params.mutateConfigFile) {
95
+ await params.mutateConfigFile({
96
+ afterWrite: { mode: "auto" },
97
+ mutate(draft) {
98
+ Object.assign(draft, buildLoginConfig(draft, result));
99
+ },
100
+ });
101
+ return;
102
+ }
103
+
104
+ if (params.persistConfig) {
105
+ await params.persistConfig(buildLoginConfig(params.cfg, result));
106
+ return;
107
+ }
108
+
109
+ throw new Error("openclaw-clawchat: mutateConfigFile is required to persist login credentials");
110
+ }
111
+
63
112
  /**
64
113
  * Run the `openclaw channels login --channel openclaw-clawchat` flow:
65
114
  * 1. Read the existing channel section; require `baseUrl` to be set so we
@@ -67,7 +116,7 @@ async function promptInviteCodeFromStdin(runtime: {
67
116
  * 2. Prompt the user for an invite code on stdin.
68
117
  * 3. POST it to `${baseUrl}/v1/agents/connect`.
69
118
  * 4. Write the returned `websocket_url` / `token` / `user_id` back into
70
- * the config so subsequent `openclaw gateway run` picks them up.
119
+ * the config so subsequent Gateway runs pick them up.
71
120
  *
72
121
  * Errors surface with clear messages (missing baseUrl, empty invite,
73
122
  * server-side rejection) so the caller can relay them to the operator.
@@ -115,34 +164,13 @@ export async function runOpenclawClawlingLogin(params: LoginParams): Promise<voi
115
164
  );
116
165
  }
117
166
 
118
- // Merge credentials into cfg.channels.openclaw-clawchat and persist
119
- // immediately so a subsequent `openclaw gateway run` picks them up
120
- // without any manual edit. `baseUrl` / `websocketUrl` stay untouched —
121
- // the built-in defaults (or operator overrides) remain authoritative
122
- // because `/v1/agents/connect` doesn't return them.
123
- const channels = (cfg.channels ?? {}) as Record<string, unknown>;
124
- const existing = (channels[CHANNEL_ID] ?? {}) as Record<string, unknown>;
125
- const nextSection: Record<string, unknown> = {
126
- ...existing,
127
- enabled: true,
128
- token: result.access_token,
129
- userId: result.agent.user_id,
130
- };
131
- if (result.refresh_token) {
132
- nextSection.refreshToken = result.refresh_token;
133
- }
134
- const nextCfg: OpenClawConfig = mergeOpenclawClawchatToolAllow({
135
- ...cfg,
136
- channels: { ...channels, [CHANNEL_ID]: nextSection },
137
- });
138
-
139
167
  const tokenPreview = redactToken(result.access_token);
140
168
  runtime.log(
141
169
  `Updating config: channels.${CHANNEL_ID}.token=${tokenPreview} userId=${result.agent.user_id}${
142
170
  result.refresh_token ? " refreshToken=***" : ""
143
171
  } …`,
144
172
  );
145
- await (params.persistConfig ?? writeConfigFile)(nextCfg);
173
+ await persistLoginConfig(params, result);
146
174
  runtime.log(`Config file updated.`);
147
175
 
148
176
  runtime.log(
@@ -6,11 +6,24 @@ import packageJson from "../package.json" with { type: "json" };
6
6
  interface PackageJsonWithOpenclaw {
7
7
  name: string;
8
8
  files: string[];
9
+ scripts: Record<string, string>;
10
+ devDependencies: Record<string, string>;
11
+ peerDependencies: Record<string, string>;
9
12
  openclaw: {
10
13
  extensions: string[];
14
+ runtimeExtensions?: string[];
11
15
  setupEntry?: string;
12
- channel?: { id: string; aliases?: string[]; cliAddOptions?: Array<{ flags: string }> };
13
- install: { npmSpec: string };
16
+ channel?: {
17
+ id: string;
18
+ label: string;
19
+ selectionLabel?: string;
20
+ docsPath?: string;
21
+ docsLabel?: string;
22
+ blurb: string;
23
+ order?: number;
24
+ aliases?: string[];
25
+ };
26
+ install: { npmSpec: string; minHostVersion: string };
14
27
  };
15
28
  }
16
29
 
@@ -31,23 +44,52 @@ describe("openclaw-clawchat manifest", () => {
31
44
  expect(pkg.openclaw.install.npmSpec).toBe("@newbase-clawchat/openclaw-clawchat");
32
45
  });
33
46
 
47
+ it("requires an OpenClaw host with runtime config mutation support", () => {
48
+ const pkg = packageJson as PackageJsonWithOpenclaw;
49
+ expect(pkg.peerDependencies.openclaw).toBe("^2026.4.29");
50
+ expect(pkg.devDependencies.openclaw).toBe("2026.4.29");
51
+ expect(pkg.openclaw.install.minHostVersion).toBe(">=2026.4.29");
52
+ });
53
+
54
+ it("publishes compiled runtime entrypoints for npm plugin installs", () => {
55
+ const pkg = packageJson as PackageJsonWithOpenclaw;
56
+ expect(pkg.openclaw.extensions).toEqual(["./index.ts"]);
57
+ expect(pkg.openclaw.runtimeExtensions).toEqual(["./dist/index.js"]);
58
+ expect(pkg.files).toContain("dist");
59
+ expect(pkg.scripts.build).toBe("tsc -p tsconfig.build.json");
60
+ expect(pkg.scripts.prepack).toBe("npm run build");
61
+ expect(fs.existsSync(new URL("../tsconfig.build.json", import.meta.url))).toBe(true);
62
+ });
63
+
64
+ it("publishes channel catalog metadata for OpenClaw CLI discovery", () => {
65
+ const pkg = packageJson as PackageJsonWithOpenclaw;
66
+ expect(pkg.openclaw.channel).toEqual({
67
+ id: "openclaw-clawchat",
68
+ label: "Clawling Chat",
69
+ selectionLabel: "Clawling Chat",
70
+ docsPath: "/channels/openclaw-clawchat",
71
+ docsLabel: "openclaw-clawchat",
72
+ blurb: "Clawling Protocol v2 over WebSocket (chat-sdk).",
73
+ order: 110,
74
+ });
75
+ });
76
+
34
77
  it("declares supported channel/command activation hints for plugin loading", () => {
35
78
  expect(pluginManifest.activation).toEqual({
79
+ onStartup: true,
36
80
  onChannels: ["openclaw-clawchat"],
37
81
  onCommands: ["clawchat-login"],
38
82
  });
39
- expect(pluginManifest.activation).not.toHaveProperty("onStartup");
40
83
  expect(pluginManifest.commandAliases).toEqual([
41
84
  { name: "clawchat-login", kind: "runtime-slash" },
42
85
  ]);
43
86
  });
44
87
 
45
- it("does not publish setup migration or setup-runtime channel metadata", () => {
88
+ it("does not publish setup migration or setup-runtime entry metadata", () => {
46
89
  const pkg = packageJson as PackageJsonWithOpenclaw;
47
90
  expect(pkg.files).not.toContain("setup-api.ts");
48
91
  expect(pkg.files).not.toContain("setup-entry.ts");
49
92
  expect(pkg.openclaw.setupEntry).toBeUndefined();
50
- expect(pkg.openclaw.channel).toBeUndefined();
51
93
  expect(fs.existsSync(new URL("../setup-api.ts", import.meta.url))).toBe(false);
52
94
  expect(fs.existsSync(new URL("../setup-entry.ts", import.meta.url))).toBe(false);
53
95
  });
@@ -73,16 +115,50 @@ describe("openclaw-clawchat manifest", () => {
73
115
  expect(skill).not.toMatch(/clawchat_activate/);
74
116
  });
75
117
 
76
- it("keeps the activation skill on channel login without tool or slash-command dispatch", () => {
118
+ it("declares ownership of registered ClawChat agent tools", () => {
119
+ expect(pluginManifest.contracts?.tools).toEqual([
120
+ "clawchat_activate",
121
+ "clawchat_get_account_profile",
122
+ "clawchat_get_user_profile",
123
+ "clawchat_list_account_friends",
124
+ "clawchat_update_account_profile",
125
+ "clawchat_upload_avatar_image",
126
+ "clawchat_upload_media_file",
127
+ ]);
128
+ });
129
+
130
+ it("keeps the optional OpenClaw source checkout local-only", () => {
131
+ expect(fs.existsSync(new URL("../.gitmodules", import.meta.url))).toBe(false);
132
+
133
+ const gitignore = fs.readFileSync(new URL("../.gitignore", import.meta.url), "utf8");
134
+ expect(gitignore).toMatch(/^tmp\/openclaw\/$/m);
135
+
136
+ const pkg = packageJson as PackageJsonWithOpenclaw;
137
+ expect(pkg.scripts["dev:openclaw-source"]).toBe(
138
+ "test -d tmp/openclaw || git clone --depth=1 https://github.com/openclaw/openclaw.git tmp/openclaw",
139
+ );
140
+
141
+ const readme = fs.readFileSync(new URL("../README.md", import.meta.url), "utf8");
142
+ expect(readme).toMatch(/npm run dev:openclaw-source/);
143
+ expect(readme).toMatch(
144
+ /git clone --depth=1 https:\/\/github\.com\/openclaw\/openclaw\.git tmp\/openclaw/,
145
+ );
146
+ expect(readme).toMatch(/local-only/i);
147
+ });
148
+
149
+ it("keeps default Vitest discovery scoped to plugin sources", () => {
150
+ const configUrl = new URL("../vitest.config.ts", import.meta.url);
151
+ expect(fs.existsSync(configUrl)).toBe(true);
152
+ const config = fs.readFileSync(configUrl, "utf8");
153
+ expect(config).toMatch(/include:\s*\["src\/\*\*\/\*\.test\.ts"\]/);
154
+ expect(config).toMatch(/"tmp\/\*\*"/);
155
+ expect(config).toMatch(/"\.e2e\/\*\*"/);
156
+ });
157
+
158
+ it("keeps the activation skill on clawchat_activate with channel-login fallback", () => {
77
159
  const skill = fs.readFileSync(new URL("../skills/clawchat-activate/SKILL.md", import.meta.url), "utf8");
78
160
  expect(skill).toMatch(/name:\s*clawchat-activate/);
79
- expect(skill).not.toMatch(/clawchat_activate/);
80
- expect(skill).not.toMatch(/command-dispatch:\s*tool/);
81
- expect(skill).not.toMatch(/command-tool:/);
82
- expect(skill).not.toMatch(/command-dispatch:/);
83
- expect(skill).not.toMatch(/command-command:/);
84
- expect(skill).not.toMatch(/command-arg-mode:/);
85
- expect(skill).not.toMatch(/user-invocable:/);
161
+ expect(skill).toMatch(/clawchat_activate/);
86
162
  expect(skill).not.toMatch(/`clawchat\s+A1B2C3`/i);
87
163
  expect(skill).not.toMatch(/`clawchat\s*<code>`/i);
88
164
  expect(skill).not.toMatch(/\/clawchat_activate A1B2C3/);
@@ -92,33 +168,33 @@ describe("openclaw-clawchat manifest", () => {
92
168
  expect(skill).toMatch(/do not append/i);
93
169
  expect(skill).toMatch(/prompt[^\n]+invite code[^\n]+provide/i);
94
170
  expect(skill).toMatch(/channel login/i);
171
+ expect(skill).toMatch(/openclaw channels status --probe/);
95
172
  expect(skill).toMatch(/openclaw gateway restart/);
96
173
  expect(skill).not.toMatch(/ask the user to send/i);
97
174
  expect(skill).not.toMatch(/give the exact/i);
98
- expect(skill).toMatch(/execute[^\n]+openclaw channels login --channel openclaw-clawchat/i);
99
- expect(skill).toMatch(/execute[^\n]+openclaw gateway restart/i);
175
+ expect(skill).toMatch(/restart[^\n]+only when/i);
100
176
  });
101
177
 
102
- it("documents channel login as the natural-language activation path", () => {
178
+ it("documents clawchat_activate as the natural-language activation path with CLI fallback", () => {
103
179
  const readme = fs.readFileSync(new URL("../README.md", import.meta.url), "utf8");
104
180
  const docs = fs.readFileSync(new URL("../docs/openclaw-clawchat.md", import.meta.url), "utf8");
181
+ expect(readme).toMatch(/clawchat_activate/i);
182
+ expect(docs).toMatch(/clawchat_activate/i);
105
183
  expect(readme).toMatch(/openclaw channels login --channel openclaw-clawchat/i);
106
184
  expect(docs).toMatch(/openclaw channels login --channel openclaw-clawchat/i);
185
+ expect(readme).toMatch(/openclaw channels status --probe/i);
186
+ expect(docs).toMatch(/openclaw channels status --probe/i);
107
187
  expect(readme).toMatch(/openclaw gateway restart/i);
108
188
  expect(docs).toMatch(/openclaw gateway restart/i);
109
189
  expect(readme).not.toMatch(/activation skill[^.]+\/clawchat-login/i);
110
190
  expect(docs).not.toMatch(/natural-language activation requests[^.]+\/clawchat-login/i);
111
191
  expect(readme).not.toMatch(/\/clawchat-activate\s+A1B2C3/i);
112
192
  expect(docs).not.toMatch(/\/clawchat-activate\s+A1B2C3/i);
113
- expect(readme).not.toMatch(/clawchat_activate/);
114
- expect(docs).not.toMatch(/clawchat_activate/);
115
193
  expect(readme).not.toMatch(/\/clawchat_activate\s+A1B2C3/i);
116
194
  expect(docs).not.toMatch(/\/clawchat_activate\s+A1B2C3/i);
117
195
  expect(readme).not.toMatch(/direct users to/i);
118
196
  expect(docs).not.toMatch(/direct the\s+user/i);
119
- expect(docs).not.toMatch(/call the tool with the extracted code/i);
120
- expect(docs).not.toMatch(/AGT->>PLG: clawchat_activate/);
121
- expect(readme).toMatch(/activation skill[^.]+execute/i);
122
- expect(docs).toMatch(/natural-language activation requests[^.]+execute/i);
197
+ expect(readme).toMatch(/activation skill calls/i);
198
+ expect(docs).toMatch(/Natural-language activation requests should call `clawchat_activate`/i);
123
199
  });
124
200
  });
@@ -65,9 +65,9 @@ describe("openclaw-clawchat outbound", () => {
65
65
  });
66
66
  expect((client.sendMessage as ReturnType<typeof vi.fn>).mock.calls[0][0]).toMatchObject({
67
67
  chat_id: "user-1",
68
- chat_type: "direct",
69
68
  body: { fragments: [{ kind: "text", text: "hello" }] },
70
69
  });
70
+ expect((client.sendMessage as ReturnType<typeof vi.fn>).mock.calls[0][0]).not.toHaveProperty("chat_type");
71
71
  expect(client.replyMessage).not.toHaveBeenCalled();
72
72
  expect(result?.messageId).toBe("server-m1");
73
73
  expect(result?.acceptedAt).toBe(1234);
@@ -78,26 +78,27 @@ describe("openclaw-clawchat outbound", () => {
78
78
  await sendOpenclawClawlingText({
79
79
  client,
80
80
  account: baseAccount(),
81
- to: { chatId: "user-1", chatType: "direct" },
81
+ to: { chatId: "chat-1", chatType: "direct" },
82
82
  text: "reply",
83
83
  replyCtx: {
84
84
  replyToMessageId: "m-orig",
85
+ replyPreviewChatId: "chat-1",
85
86
  replyPreviewSenderId: "user-2",
86
87
  replyPreviewNickName: "Sender",
87
88
  replyPreviewText: "original",
88
89
  },
89
90
  });
90
91
  expect((client.replyMessage as ReturnType<typeof vi.fn>).mock.calls[0][0]).toMatchObject({
91
- chat_id: "user-1",
92
- chat_type: "direct",
92
+ chat_id: "chat-1",
93
93
  replyTo: {
94
94
  msgId: "m-orig",
95
- senderId: "user-2",
95
+ senderId: "chat-1",
96
96
  nickName: "Sender",
97
97
  fragments: [{ kind: "text", text: "original" }],
98
98
  },
99
99
  body: { fragments: [{ kind: "text", text: "reply" }] },
100
100
  });
101
+ expect((client.replyMessage as ReturnType<typeof vi.fn>).mock.calls[0][0]).not.toHaveProperty("chat_type");
101
102
  expect(client.sendMessage).not.toHaveBeenCalled();
102
103
  });
103
104
 
package/src/outbound.ts CHANGED
@@ -22,6 +22,7 @@ export interface OutboundTarget {
22
22
 
23
23
  export interface OutboundReplyCtx {
24
24
  replyToMessageId: string;
25
+ replyPreviewChatId?: string;
25
26
  replyPreviewSenderId: string;
26
27
  replyPreviewNickName: string;
27
28
  replyPreviewText: string;
@@ -38,6 +39,7 @@ export interface SendParams {
38
39
  to: OutboundTarget;
39
40
  text: string;
40
41
  replyCtx?: OutboundReplyCtx;
42
+ richFragments?: Fragment[];
41
43
  mediaFragments?: ClawlingMediaFragment[];
42
44
  mentions?: string[];
43
45
  log?: LogSink;
@@ -92,8 +94,9 @@ export function parseOpenclawRecipient(to: string): { chatId: string; chatType:
92
94
 
93
95
  export async function sendOpenclawClawlingText(params: SendParams): Promise<SendResult | null> {
94
96
  const text = (params.text ?? "").trim();
97
+ const richFragments = params.richFragments ?? [];
95
98
  const mediaFragments = params.mediaFragments ?? [];
96
- if (!text && mediaFragments.length === 0) {
99
+ if (!text && richFragments.length === 0 && mediaFragments.length === 0) {
97
100
  params.log?.info?.(
98
101
  `[${params.account.accountId}] openclaw-clawchat outbound suppressed: empty text and no media`,
99
102
  );
@@ -106,7 +109,7 @@ export async function sendOpenclawClawlingText(params: SendParams): Promise<Send
106
109
  // with one of the SDK's narrow Fragment members (ImageFragment / FileFragment /
107
110
  // AudioFragment / VideoFragment) based on its runtime `kind`. The wide local
108
111
  // shape lets us build a single uniform array without a per-kind switch.
109
- const fragments = [...textFragments, ...mediaFragments] as Fragment[];
112
+ const fragments = [...textFragments, ...richFragments, ...mediaFragments] as Fragment[];
110
113
 
111
114
  const useReply = params.replyCtx && mediaFragments.length === 0;
112
115
  if (params.replyCtx && mediaFragments.length > 0) {
@@ -121,26 +124,24 @@ export async function sendOpenclawClawlingText(params: SendParams): Promise<Send
121
124
  mode = "reply";
122
125
  ack = await params.client.replyMessage({
123
126
  chat_id: params.to.chatId,
124
- chat_type: params.to.chatType,
125
127
  mode: "normal",
126
128
  replyTo: {
127
129
  msgId: params.replyCtx.replyToMessageId,
128
- senderId: params.replyCtx.replyPreviewSenderId,
130
+ senderId: params.replyCtx.replyPreviewChatId ?? params.replyCtx.replyPreviewSenderId,
129
131
  nickName: params.replyCtx.replyPreviewNickName,
130
132
  fragments: [{ kind: "text", text: params.replyCtx.replyPreviewText }],
131
133
  },
132
134
  body: { fragments },
133
135
  context: { mentions },
134
- });
136
+ } as Parameters<ClawlingChatClient["replyMessage"]>[0]);
135
137
  } else {
136
138
  mode = "send";
137
139
  ack = await params.client.sendMessage({
138
140
  chat_id: params.to.chatId,
139
- chat_type: params.to.chatType,
140
141
  mode: "normal",
141
142
  body: { fragments },
142
143
  context: { mentions, reply: null },
143
- });
144
+ } as Parameters<ClawlingChatClient["sendMessage"]>[0]);
144
145
  }
145
146
  params.log?.info?.(
146
147
  `[${params.account.accountId}] openclaw-clawchat outbound mode=${mode} msg=${ack.payload.message_id} text_len=${text.length} media=${mediaFragments.length} trace=${ack.trace_id}`,