diff --git a/src/auto-reply/reply/block-streaming.test.ts b/src/auto-reply/reply/block-streaming.test.ts index ad4d3ee936f..475b6dfcbf8 100644 --- a/src/auto-reply/reply/block-streaming.test.ts +++ b/src/auto-reply/reply/block-streaming.test.ts @@ -99,6 +99,52 @@ describe("resolveEffectiveBlockStreamingConfig", () => { ).toMatchObject({ minChars: 10, maxChars: 40, idleMs: 2 }); }); + it("merges partial account nested block coalescing over channel config", () => { + const cfg = { + channels: { + imessage: { + streaming: { block: { coalesce: { minChars: 25, maxChars: 80, idleMs: 5 } } }, + accounts: { + personal: { + streaming: { block: { coalesce: { idleMs: 2 } } }, + }, + }, + }, + }, + } as OpenClawConfig; + + expect( + resolveEffectiveBlockStreamingConfig({ + cfg, + provider: "imessage", + accountId: "personal", + }).coalescing, + ).toMatchObject({ minChars: 25, maxChars: 80, idleMs: 2 }); + }); + + it("merges legacy account block coalescing over channel nested config", () => { + const cfg = { + channels: { + imessage: { + streaming: { block: { coalesce: { minChars: 25, maxChars: 80, idleMs: 5 } } }, + accounts: { + personal: { + blockStreamingCoalesce: { idleMs: 2 }, + }, + }, + }, + }, + } as OpenClawConfig; + + expect( + resolveEffectiveBlockStreamingConfig({ + cfg, + provider: "imessage", + accountId: "personal", + }).coalescing, + ).toMatchObject({ minChars: 25, maxChars: 80, idleMs: 2 }); + }); + it("allows ACP maxChunkChars overrides above base defaults up to provider text limits", () => { const cfg = { channels: { diff --git a/src/auto-reply/reply/block-streaming.ts b/src/auto-reply/reply/block-streaming.ts index e2e05ba3fd8..ef0560130b4 100644 --- a/src/auto-reply/reply/block-streaming.ts +++ b/src/auto-reply/reply/block-streaming.ts @@ -39,6 +39,14 @@ type ProviderBlockStreamingConfig = { >; }; +function resolveScopedBlockStreamingCoalesce( + config: ProviderBlockStreamingConfig | undefined, +): BlockStreamingCoalesceConfig | undefined { + return config + ? (resolveChannelStreamingBlockCoalesce(config) ?? config.blockStreamingCoalesce) + : undefined; +} + function resolveProviderBlockStreamingCoalesce(params: { cfg: OpenClawConfig | undefined; providerKey?: TextChunkProvider; @@ -57,12 +65,12 @@ function resolveProviderBlockStreamingCoalesce(params: { const normalizedAccountId = normalizeAccountId(accountId); const typed = providerCfg as ProviderBlockStreamingConfig; const accountCfg = resolveAccountEntry(typed.accounts, normalizedAccountId); - return ( - resolveChannelStreamingBlockCoalesce(accountCfg) ?? - resolveChannelStreamingBlockCoalesce(typed) ?? - accountCfg?.blockStreamingCoalesce ?? - typed.blockStreamingCoalesce - ); + const channelCoalesce = resolveScopedBlockStreamingCoalesce(typed); + const accountCoalesce = resolveScopedBlockStreamingCoalesce(accountCfg); + if (channelCoalesce || accountCoalesce) { + return { ...channelCoalesce, ...accountCoalesce }; + } + return undefined; } export type BlockStreamingCoalescing = {