fix(auto-reply): preserve silent voice payloads · openclaw/openclaw@28bf71d
steipete
·
2026-04-28
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
14 | 14 | ### Fixes |
15 | 15 | |
16 | 16 | - Auto-reply/commands: stop bare `/reset` and `/new` after reset hooks acknowledge the command, so non-ACP channels no longer fall through into empty provider calls while `/reset <message>` and `/new <message>` still seed the next model turn. Fixes #73367. Thanks @hoyanhan and @wenxu007. |
| 17 | +- Auto-reply: preserve voice-note media from silent turns while continuing to suppress text and non-voice media, so `NO_REPLY` TTS replies still deliver the requested audio bubble. (#73406) Thanks @zqchris. |
17 | 18 | - Agents/Anthropic: send implicit Anthropic beta headers only to direct public Anthropic endpoints, including OAuth, so custom Anthropic-compatible providers no longer mis-handle unsupported beta flags unless explicitly configured. Refs #73346. Thanks @byBrodowski. |
18 | 19 | - Skills: require explicit `skills.entries.coding-agent.enabled` before exposing the bundled coding-agent skill, so installs with Codex on PATH but no OpenAI auth do not silently offer Codex delegation. Fixes #73358. Thanks @LaFleurAdvertising and @Sanjays2402. |
19 | 20 | - Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock and keep Docker bundled plugin runtime deps/mirrors in a Docker-managed volume instead of the Windows/WSL config bind mount, so cold starts avoid slow host-volume mirror writes. Fixes #73339. Thanks @1yihui. |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -371,7 +371,7 @@ describe("buildReplyPayloads media filter integration", () => {
|
371 | 371 | }); |
372 | 372 | }); |
373 | 373 | |
374 | | -it("drops all final payloads during silent turns, including media-only payloads", async () => { |
| 374 | +it("drops non-voice final payloads during silent turns, including media-only payloads", async () => { |
375 | 375 | const { replyPayloads } = await buildReplyPayloads({ |
376 | 376 | ...baseParams, |
377 | 377 | silentExpected: true, |
@@ -381,6 +381,31 @@ describe("buildReplyPayloads media filter integration", () => {
|
381 | 381 | expect(replyPayloads).toHaveLength(0); |
382 | 382 | }); |
383 | 383 | |
| 384 | +it("keeps voice media payloads during silent turns", async () => { |
| 385 | +const { replyPayloads } = await buildReplyPayloads({ |
| 386 | + ...baseParams, |
| 387 | +silentExpected: true, |
| 388 | +payloads: [{ text: "NO_REPLY", mediaUrl: "file:///tmp/voice.opus", audioAsVoice: true }], |
| 389 | +}); |
| 390 | + |
| 391 | +expect(replyPayloads).toHaveLength(1); |
| 392 | +expect(replyPayloads[0]).toMatchObject({ |
| 393 | +text: undefined, |
| 394 | +mediaUrl: "file:///tmp/voice.opus", |
| 395 | +audioAsVoice: true, |
| 396 | +}); |
| 397 | +}); |
| 398 | + |
| 399 | +it("drops empty voice markers during silent turns", async () => { |
| 400 | +const { replyPayloads } = await buildReplyPayloads({ |
| 401 | + ...baseParams, |
| 402 | +silentExpected: true, |
| 403 | +payloads: [{ audioAsVoice: true }], |
| 404 | +}); |
| 405 | + |
| 406 | +expect(replyPayloads).toHaveLength(0); |
| 407 | +}); |
| 408 | + |
384 | 409 | it("suppresses warning text when silent media payloads fail normalization", async () => { |
385 | 410 | const normalizeMediaPaths = async () => { |
386 | 411 | throw new Error("file not found"); |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -87,6 +87,10 @@ async function normalizeSentMediaUrlsForDedupe(params: {
|
87 | 87 | return normalizedUrls; |
88 | 88 | } |
89 | 89 | |
| 90 | +function shouldKeepPayloadDuringSilentTurn(payload: ReplyPayload): boolean { |
| 91 | +return payload.audioAsVoice === true && resolveSendableOutboundReplyParts(payload).hasMedia; |
| 92 | +} |
| 93 | + |
90 | 94 | export async function buildReplyPayloads(params: { |
91 | 95 | payloads: ReplyPayload[]; |
92 | 96 | isHeartbeat: boolean; |
@@ -165,7 +169,9 @@ export async function buildReplyPayloads(params: {
|
165 | 169 | }), |
166 | 170 | ) |
167 | 171 | ).filter(isRenderablePayload); |
168 | | -const silentFilteredPayloads = params.silentExpected ? [] : replyTaggedPayloads; |
| 172 | +const silentFilteredPayloads = params.silentExpected |
| 173 | + ? replyTaggedPayloads.filter(shouldKeepPayloadDuringSilentTurn) |
| 174 | + : replyTaggedPayloads; |
169 | 175 | |
170 | 176 | // Drop final payloads only when block streaming succeeded end-to-end. |
171 | 177 | // If streaming aborted (e.g., timeout), fall back to final payloads. |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。