diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f5a036a..7645f0e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ - Auth: default billing disable backoff to 5h (doubling, 24h cap) and surface disabled/cooldown profiles in `models list` + doctor. (#486) — thanks @steipete - Commands: harden slash command registry and list text-only commands in `/commands`. - Models/Auth: show per-agent auth candidates in `/model status`, and add `clawdbot models auth order {get,set,clear}` (per-agent auth rotation overrides). — thanks @steipete +- Telegram: keep streamMode draft-only; avoid forcing block streaming. (#619) — thanks @rubyrunsstuff - Debugging: add raw model stream logging flags and document gateway watch mode. - Gateway: decode dns-sd escaped UTF-8 in discovery output and show scan progress immediately. — thanks @steipete - Agent: add claude-cli/opus-4.5 runner via Claude CLI with resume support (tools disabled). diff --git a/src/agents/pi-embedded-block-chunker.ts b/src/agents/pi-embedded-block-chunker.ts index 7254bbd9f..3bf95c14b 100644 --- a/src/agents/pi-embedded-block-chunker.ts +++ b/src/agents/pi-embedded-block-chunker.ts @@ -221,6 +221,10 @@ export class EmbeddedBlockChunker { if (sentenceIdx >= minChars) return { index: sentenceIdx }; } + if (preference === "newline" && buffer.length < maxChars) { + return { index: -1 }; + } + for (let i = window.length - 1; i >= minChars; i--) { if (/\s/.test(window[i]) && isSafeFenceBreak(fenceSpans, i)) { return { index: i }; diff --git a/src/auto-reply/reply.block-streaming.test.ts b/src/auto-reply/reply.block-streaming.test.ts index 8cb8ab767..9a13e65cc 100644 --- a/src/auto-reply/reply.block-streaming.test.ts +++ b/src/auto-reply/reply.block-streaming.test.ts @@ -273,4 +273,45 @@ describe("block streaming", () => { expect(sawAbort).toBe(true); }); }); + + it("does not enable block streaming for telegram streamMode block", async () => { + await withTempHome(async (home) => { + const onBlockReply = vi.fn().mockResolvedValue(undefined); + + const impl = async () => ({ + payloads: [{ text: "final" }], + meta: { + durationMs: 5, + agentMeta: { sessionId: "s", provider: "p", model: "m" }, + }, + }); + piEmbeddedMock.runEmbeddedPiAgent.mockImplementation(impl); + + const res = await getReplyFromConfig( + { + Body: "ping", + From: "+1004", + To: "+2000", + MessageSid: "msg-126", + Provider: "telegram", + }, + { + onBlockReply, + }, + { + agents: { + defaults: { + model: "anthropic/claude-opus-4-5", + workspace: path.join(home, "clawd"), + }, + }, + telegram: { allowFrom: ["*"], streamMode: "block" }, + session: { store: path.join(home, "sessions.json") }, + }, + ); + + expect(res?.text).toBe("final"); + expect(onBlockReply).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/auto-reply/reply/block-streaming.ts b/src/auto-reply/reply/block-streaming.ts index 17f2cda43..da60e1ce9 100644 --- a/src/auto-reply/reply/block-streaming.ts +++ b/src/auto-reply/reply/block-streaming.ts @@ -58,9 +58,10 @@ export function resolveBlockStreamingChunking( Math.floor(chunkCfg?.maxChars ?? DEFAULT_BLOCK_STREAM_MAX), ); const maxChars = Math.max(1, Math.min(maxRequested, textLimit)); + const minFallback = DEFAULT_BLOCK_STREAM_MIN; const minRequested = Math.max( 1, - Math.floor(chunkCfg?.minChars ?? DEFAULT_BLOCK_STREAM_MIN), + Math.floor(chunkCfg?.minChars ?? minFallback), ); const minChars = Math.min(minRequested, maxChars); const breakPreference = @@ -80,7 +81,7 @@ export function resolveBlockStreamingCoalescing( maxChars: number; breakPreference: "paragraph" | "newline" | "sentence"; }, -): BlockStreamingCoalescing { +): BlockStreamingCoalescing | undefined { const providerKey = normalizeChunkProvider(provider); const textLimit = resolveTextChunkLimit(cfg, providerKey, accountId); const normalizedAccountId = normalizeAccountId(accountId); diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index c5979ec76..aaba18307 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -826,6 +826,12 @@ export function createTelegramBot(opts: TelegramBotOptions) { onReplyStart: sendTyping, }); + const disableBlockStreaming = + Boolean(draftStream) || + (typeof telegramCfg.blockStreaming === "boolean" + ? !telegramCfg.blockStreaming + : undefined); + const { queuedFinal } = await dispatchReplyFromConfig({ ctx: ctxPayload, cfg, @@ -841,11 +847,7 @@ export function createTelegramBot(opts: TelegramBotOptions) { if (payload.text) draftStream.update(payload.text); } : undefined, - disableBlockStreaming: - Boolean(draftStream) || - (typeof telegramCfg.blockStreaming === "boolean" - ? !telegramCfg.blockStreaming - : undefined), + disableBlockStreaming, }, }); markDispatchIdle();