Skip to content

Commit 66afc12

Browse files
authored
Merge branch 'main' into fix-memory-session-startup-catchup-62625
2 parents 7a82279 + c4f20b6 commit 66afc12

45 files changed

Lines changed: 623 additions & 72 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
Docs: https://docs.openclaw.ai
44

5+
## Unreleased
6+
7+
### Fixes
8+
9+
- WhatsApp: honor forced document delivery for outbound image, GIF, and video media so `forceDocument`/`asDocument` sends preserve original media bytes instead of using compressed media payloads. (#79272) Thanks @itsuzef.
10+
511
## 2026.5.17
612

713
### Changes
@@ -29,10 +35,16 @@ Docs: https://docs.openclaw.ai
2935

3036
### Fixes
3137

38+
- TUI: restore the submitted draft when chat is busy instead of clearing it or queueing another run. Fixes #45326. (#82774) Thanks @hyspacex.
39+
- Browser plugin: redact attach-details from Chrome MCP diagnostics and keep raw Chrome launch error output around long enough to surface in user reports without leaking sensitive paths.
40+
- System prompts: clarify MEMORY guidance over generic TTS hints in the embedded speech-core/system-prompt scaffolding so agents prefer memory-store usage over speech defaults. Fixes #81930. Thanks @giodl73-repo.
41+
- Agents/auth: include the checked credential source in missing API key errors, so users can see which env var, profile, or config path to fix. Fixes #82785. Thanks @loeclos.
3242
- Providers/GitHub Copilot: hash Responses replay item ids with sha256 instead of a weak 32-bit hash and build same-provider Copilot tool-call ids distinctly, so concurrent tool-call replays no longer collide and reject follow-up turns.
43+
- Agents/replay: normalize malformed assistant replay content before transport conversion while preserving empty-stop replay repair, so bad provider history no longer crashes with non-iterable content. Fixes #43795. (#82748) Thanks @IWhatsskill.
3344
- Providers/Anthropic-messages: extract `reasoning_content` from `thinking` blocks during assistant replay so proxy providers that route through the Anthropic-messages transport preserve reasoning context across tool-call follow-up turns. Thanks @Sunnyone2three.
3445
- Agents/GitHub Copilot: normalize replayed Responses tool-call IDs before dispatch so resumed sessions with historical overlong tool IDs continue instead of failing Copilot schema validation. (#82750) Thanks @galiniliev.
3546
- CLI/web: resolve provider-scoped web search/fetch SecretRefs for `infer web ... --provider ...` while leaving unrelated plugin secrets untouched. Fixes #82621. Thanks @leno23.
47+
- Providers/Anthropic Vertex: resolve installed provider public surfaces from package-local `dist/`, restoring `anthropic-vertex/*` model calls after plugin externalization. Fixes #82781. Thanks @0L1v3DaD.
3648
- Mac app: let menu gateway/session error text wrap across a few lines and stop rebuilding dynamic Context/Gateway menu rows while the menu is open, reducing flicker.
3749
- Mac app: make device pairing approval sheets friendlier, with concise Mac/device copy, shortened identifiers, friendly scope labels, and Approve as the primary action.
3850
- Providers/Qwen: honor session thinking level for `qwen-chat-template` payloads so `/think off` disables nested llama.cpp chat-template thinking controls. Fixes #82768. Thanks @bfox55.

docs/channels/whatsapp.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s
393393
- non-Ogg audio, including Microsoft Edge TTS MP3/WebM output, is transcoded with `ffmpeg` to 48 kHz mono Ogg/Opus before PTT delivery
394394
- `/tts latest` sends the latest assistant reply as one voice note and suppresses repeat sends for the same reply; `/tts chat on|off|default` controls auto-TTS for the current WhatsApp chat
395395
- animated GIF playback is supported via `gifPlayback: true` on video sends
396+
- `forceDocument` / `asDocument` sends outbound images, GIFs, and videos through the Baileys document payload to avoid WhatsApp media compression while preserving the resolved filename and MIME type
396397
- captions are applied to the first media item when sending multi-media reply payloads, except PTT voice notes send the audio first and visible text separately because WhatsApp clients do not render voice-note captions consistently
397398
- media source can be HTTP(S), `file://`, or local paths
398399

@@ -402,7 +403,7 @@ When the linked self number is also present in `allowFrom`, WhatsApp self-chat s
402403
- inbound media save cap: `channels.whatsapp.mediaMaxMb` (default `50`)
403404
- outbound media send cap: `channels.whatsapp.mediaMaxMb` (default `50`)
404405
- per-account overrides use `channels.whatsapp.accounts.<accountId>.mediaMaxMb`
405-
- images are auto-optimized (resize/quality sweep) to fit limits
406+
- images are auto-optimized (resize/quality sweep) to fit limits unless `forceDocument` / `asDocument` requests document delivery
406407
- on media send failure, first-item fallback sends text warning instead of dropping the response silently
407408

408409
</Accordion>

docs/cli/message.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Name lookup:
7272
- Optional: `--media`, `--presentation`, `--delivery`, `--pin`, `--reply-to`, `--thread-id`, `--gif-playback`, `--force-document`, `--silent`
7373
- Shared presentation payloads: `--presentation` sends semantic blocks (`text`, `context`, `divider`, `buttons`, `select`) that core renders through the selected channel's declared capabilities. See [Message Presentation](/plugins/message-presentation).
7474
- Generic delivery preferences: `--delivery` accepts delivery hints such as `{ "pin": true }`; `--pin` is shorthand for pinned delivery when the channel supports it.
75-
- Telegram only: `--force-document` (send images, GIFs, and videos as documents to avoid Telegram compression)
75+
- Telegram + WhatsApp: `--force-document` (send images, GIFs, and videos as documents to avoid channel compression)
7676
- Telegram only: `--thread-id` (forum topic id)
7777
- Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field)
7878
- Telegram + Discord: `--silent`
@@ -302,7 +302,7 @@ openclaw message send --channel msteams \
302302
--presentation '{"title":"Status update","blocks":[{"type":"text","text":"Build completed"}]}'
303303
```
304304

305-
Send a Telegram image as a document to avoid compression:
305+
Send a Telegram or WhatsApp image as a document to avoid compression:
306306

307307
```bash
308308
openclaw message send --channel telegram --target @mychat \

extensions/telegram/src/account-selection.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ function listConfiguredAccountIds(cfg: OpenClawConfig): string[] {
3737
return [...ids];
3838
}
3939

40+
function hasConfiguredDefaultAccountSource(cfg: OpenClawConfig): boolean {
41+
const telegram = cfg.channels?.telegram;
42+
if (!telegram) {
43+
return false;
44+
}
45+
const botToken = telegram.botToken;
46+
if (typeof botToken === "string") {
47+
return botToken.trim().length > 0;
48+
}
49+
if (botToken && typeof botToken === "object") {
50+
return true;
51+
}
52+
return typeof telegram.tokenFile === "string" && telegram.tokenFile.trim().length > 0;
53+
}
54+
4055
function resolveBindingAccount(params: {
4156
binding: unknown;
4257
channelId: string;
@@ -114,7 +129,10 @@ function resolveListedDefaultAccountId(params: {
114129
export function listTelegramAccountIds(cfg: OpenClawConfig): string[] {
115130
return combineAccountIds({
116131
configuredAccountIds: listConfiguredAccountIds(cfg),
117-
additionalAccountIds: listBoundAccountIds(cfg, "telegram"),
132+
additionalAccountIds: [
133+
...listBoundAccountIds(cfg, "telegram"),
134+
...(hasConfiguredDefaultAccountSource(cfg) ? [DEFAULT_ACCOUNT_ID] : []),
135+
],
118136
});
119137
}
120138

extensions/telegram/src/accounts.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,33 @@ describe("resolveTelegramAccount", () => {
145145
expect(accounts.map((account) => account.accountId)).toEqual(["work"]);
146146
expect(accounts[0]?.token).toBe("tok-work");
147147
});
148+
149+
it("keeps the implicit default account when named accounts are added to top-level credentials (#82780)", () => {
150+
const cfg = {
151+
channels: {
152+
telegram: {
153+
botToken: "tok-default",
154+
accounts: {
155+
fusion: {
156+
enabled: false,
157+
name: "Fusion",
158+
botToken: "tok-fusion",
159+
},
160+
},
161+
},
162+
},
163+
bindings: [{ agentId: "fusion", match: { channel: "telegram", accountId: "fusion" } }],
164+
} as unknown as OpenClawConfig;
165+
166+
expect(listTelegramAccountIds(cfg)).toEqual(["default", "fusion"]);
167+
expect(resolveDefaultTelegramAccountId(cfg)).toBe("default");
168+
expectNoMissingDefaultWarning();
169+
170+
const accounts = listEnabledTelegramAccounts(cfg);
171+
expect(accounts.map((account) => account.accountId)).toEqual(["default"]);
172+
expect(accounts[0]?.token).toBe("tok-default");
173+
expect(accounts[0]?.tokenSource).toBe("config");
174+
});
148175
});
149176

150177
describe("resolveDefaultTelegramAccountId", () => {

extensions/whatsapp/src/inbound/send-api.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,37 @@ describe("createWebSendApi", () => {
123123
});
124124
});
125125

126+
it("sends visual media as document when sendOptions.asDocument is true", async () => {
127+
const payload = Buffer.from("img");
128+
await api.sendMessage("+1555", "promo", payload, "image/png", {
129+
asDocument: true,
130+
fileName: "promo.png",
131+
});
132+
expect(sendMessage).toHaveBeenCalledWith(
133+
"1555@s.whatsapp.net",
134+
expect.objectContaining({
135+
document: payload,
136+
fileName: "promo.png",
137+
caption: "promo",
138+
mimetype: "image/png",
139+
}),
140+
);
141+
});
142+
143+
it("does not force audio media onto the document branch", async () => {
144+
const payload = Buffer.from("aud");
145+
await api.sendMessage("+1555", "voice", payload, "audio/ogg", {
146+
asDocument: true,
147+
fileName: "voice.ogg",
148+
});
149+
150+
expect(sendMessage).toHaveBeenCalledWith("1555@s.whatsapp.net", {
151+
audio: payload,
152+
ptt: true,
153+
mimetype: "audio/ogg",
154+
});
155+
});
156+
126157
it("sends plain text messages", async () => {
127158
const res = await api.sendMessage("+1555", "hello");
128159
expect(sendMessage).toHaveBeenCalledWith("1555@s.whatsapp.net", { text: "hello" });
@@ -199,6 +230,39 @@ describe("createWebSendApi", () => {
199230
});
200231
});
201232

233+
it("uses resolved mention caption text for forced-document media", async () => {
234+
api = createWebSendApi({
235+
sock: { sendMessage, sendPresenceUpdate },
236+
defaultAccountId: "main",
237+
resolveOutboundMentions: ({ jid, text }) =>
238+
resolveWhatsAppOutboundMentions({
239+
chatJid: jid,
240+
text,
241+
participants: [
242+
{
243+
id: "277038292303944:4@lid",
244+
phoneNumber: "5511976136970@s.whatsapp.net",
245+
},
246+
],
247+
}),
248+
});
249+
const payload = Buffer.from("img");
250+
251+
await api.sendMessage("120363000000000000@g.us", "cap @+5511976136970", payload, "image/jpeg", {
252+
asDocument: true,
253+
fileName: "promo.jpg",
254+
});
255+
256+
expectFirstSendJid("120363000000000000@g.us");
257+
expectSendContentFields(0, {
258+
document: payload,
259+
fileName: "promo.jpg",
260+
caption: "cap @277038292303944",
261+
mimetype: "image/jpeg",
262+
mentions: ["277038292303944@lid"],
263+
});
264+
});
265+
202266
it("supports audio as push-to-talk voice note", async () => {
203267
const payload = Buffer.from("aud");
204268
await api.sendMessage("+1555", "", payload, "audio/ogg", { accountId: "alt" });

extensions/whatsapp/src/inbound/send-api.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ function recordWhatsAppOutbound(accountId: string) {
2727
});
2828
}
2929

30+
function supportsForcedDocumentMediaType(mediaType: string): boolean {
31+
return mediaType.startsWith("image/") || mediaType.startsWith("video/");
32+
}
33+
3034
export function createWebSendApi(params: {
3135
sock: {
3236
sendMessage: (
@@ -79,7 +83,15 @@ export function createWebSendApi(params: {
7983
? { text, mentionedJids: [] }
8084
: await resolveMentions(jid, text);
8185
if (mediaBuffer && mediaType) {
82-
if (mediaType.startsWith("image/")) {
86+
if (sendOptions?.asDocument === true && supportsForcedDocumentMediaType(mediaType)) {
87+
const fileName = sendOptions?.fileName?.trim() || "file";
88+
payload = {
89+
document: mediaBuffer,
90+
fileName,
91+
caption: resolvedPayloadText.text || undefined,
92+
mimetype: mediaType,
93+
};
94+
} else if (mediaType.startsWith("image/")) {
8395
payload = {
8496
image: mediaBuffer,
8597
caption: resolvedPayloadText.text || undefined,

extensions/whatsapp/src/inbound/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export type ActiveWebSendOptions = {
2121
gifPlayback?: boolean;
2222
accountId?: string;
2323
fileName?: string;
24+
asDocument?: boolean;
2425
};
2526

2627
export type ActiveWebListener = {

extensions/whatsapp/src/outbound-base.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type WhatsAppSendTextOptions = {
3232
mediaReadFile?: (filePath: string) => Promise<Buffer>;
3333
gifPlayback?: boolean;
3434
audioAsVoice?: boolean;
35+
forceDocument?: boolean;
3536
accountId?: string;
3637
quotedMessageKey?: {
3738
id: string;
@@ -192,6 +193,7 @@ export function createWhatsAppOutboundBase({
192193
accountId,
193194
deps,
194195
gifPlayback,
196+
forceDocument,
195197
replyToId,
196198
}) => {
197199
const send =
@@ -214,6 +216,7 @@ export function createWhatsAppOutboundBase({
214216
...(audioAsVoice === undefined ? {} : { audioAsVoice }),
215217
accountId: accountId ?? undefined,
216218
gifPlayback,
219+
forceDocument,
217220
quotedMessageKey,
218221
});
219222
},

extensions/whatsapp/src/outbound-media-contract.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ function normalizeWhatsAppLoadedMedia(
124124
const fileName =
125125
kind === "document"
126126
? (media.fileName ?? deriveWhatsAppDocumentFileName(mediaUrl) ?? "file")
127-
: undefined;
127+
: media.fileName;
128128
return {
129129
buffer: media.buffer,
130130
kind,

0 commit comments

Comments
 (0)