diff --git a/CHANGELOG.md b/CHANGELOG.md index d26f7638bb7..9f91352465f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai - Discord/voice: keep default agent-proxy realtime sessions from auto-speaking filler before the forced OpenClaw consult answer, finish Discord playback on realtime response completion, and queue later exact-speech answers until playback idles to avoid mid-sentence replacement. - Gateway: return deterministic `400 invalid_request_error` responses for malformed encoded session-kill HTTP paths instead of letting route-shaped requests fall through to later Gateway handlers. (#72439) Thanks @rubencu. - OpenAI/realtime voice: honor disabled input-audio interruption locally so server VAD speech-start events do not clear Discord playback after operators set `interruptResponseOnInputAudio: false`. +- Telegram: keep no-response DM turns quiet instead of rewriting them into visible silent-reply chatter. Fixes #78188. (#78228) Thanks @Beandon13. - Telegram: handle managed select button callbacks before the raw callback fallback while preserving delimiter-containing option values such as `env|prod`. (#79816) Thanks @moeedahmed. - OpenAI-compatible models: handle JSON chat-completion bodies returned to streaming requests, preserving reasoning fields and visible text instead of completing an empty agent turn. Fixes #77870. - CLI/media: let explicit image description model refs use bundled static provider catalogs and generic model-backed image hooks, so `openclaw infer image describe --model zai/glm-4.6v` works like direct model runs and Anthropic auth probes avoid stale Claude 3 Haiku catalog entries. diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index 3982622ec67..4c1ecf28796 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -1771,7 +1771,7 @@ describe("dispatchTelegramMessage draft streaming", () => { expect(deliverReplies).not.toHaveBeenCalled(); }); - it("emits a silent-reply fallback when no final reply was queued and nothing was delivered", async () => { + it("does not emit a silent-reply fallback for no-response DM turns", async () => { dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ queuedFinal: false, counts: { block: 0, final: 0, tool: 0 }, @@ -1784,6 +1784,50 @@ describe("dispatchTelegramMessage draft streaming", () => { streamMode: "off", }); + expect(deliverReplies).not.toHaveBeenCalled(); + }); + + it("emits a silent-reply fallback for no-response group turns", async () => { + dispatchReplyWithBufferedBlockDispatcher.mockResolvedValue({ + queuedFinal: false, + counts: { block: 0, final: 0, tool: 0 }, + }); + + await dispatchWithContext({ + context: createContext({ + chatId: -1001234, + isGroup: true, + ctxPayload: { + SessionKey: "agent:test:telegram:group:-1001234", + ChatType: "group", + } as TelegramMessageContext["ctxPayload"], + primaryCtx: { + message: { chat: { id: -1001234, type: "supergroup" } }, + } as TelegramMessageContext["primaryCtx"], + msg: { + chat: { id: -1001234, type: "supergroup" }, + message_id: 456, + } as TelegramMessageContext["msg"], + threadSpec: { id: undefined, scope: "none" }, + replyThreadId: undefined, + }), + cfg: { + agents: { + defaults: { + silentReply: { + direct: "disallow", + group: "disallow", + internal: "allow", + }, + silentReplyRewrite: { + group: true, + }, + }, + }, + } as Parameters[0]["cfg"], + streamMode: "off", + }); + expect(deliverReplies).toHaveBeenCalledTimes(1); const replies = deliverReplies.mock.calls[0]?.[0]?.replies as | Array<{ text?: string }> diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index baf963e5b72..40cb9b2a204 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -1618,7 +1618,8 @@ export const dispatchTelegramMessage = async ({ !dispatchError && !deliverySummary.delivered && !suppressSilentReplyFallback && - !queuedFinal + !queuedFinal && + isGroup ) { const policySessionKey = ctxPayload.CommandSource === "native"