fix: bypass pipeline for compaction start notice to preserve final reply

Previously the start notice was routed through blockReplyHandler which
enqueues into blockReplyPipeline, setting didStream() = true.  This
caused buildReplyPayloads to drop all final payloads (shouldDropFinalPayloads
path), discarding the real assistant reply on non-streaming model paths
where assistantTexts is populated from the final message (not block chunks).

Fix: send the start notice directly via opts.onBlockReply, bypassing the
pipeline entirely.  applyReplyToMode is still applied so replyToId threading
(replyToMode=all|first) is honoured.  This mirrors how the completion notice
in agent-runner.ts avoids the pipeline after flush()/stop().
This commit is contained in:
zidongdesign 2026-03-08 13:06:50 +08:00 committed by Josh Lehman
parent bcc2d2188e
commit 88c9ad3026
No known key found for this signature in database
GPG Key ID: D141B425AC7F876B

View File

@ -418,14 +418,10 @@ export async function runAgentTurnWithFallback(params: {
if (phase === "start") {
if (params.opts?.onCompactionStart) {
await params.opts.onCompactionStart();
} else if (params.blockStreamingEnabled) {
// Route through the shared block reply handler so
// reply-to threading matches other in-run notices.
await blockReplyHandler?.({ text: "🧹 Compacting context..." });
} else if (params.opts?.onBlockReply) {
// blockReplyHandler is a no-op when streaming is disabled.
// Fall back to direct delivery so non-streaming runs also
// receive the compaction start notice.
// Send directly via opts.onBlockReply (bypassing the
// pipeline) so the notice does not cause final payloads
// to be discarded on non-streaming model paths.
const currentMessageId =
params.sessionCtx.MessageSidFull ?? params.sessionCtx.MessageSid;
const noticePayload = params.applyReplyToMode({
@ -434,8 +430,6 @@ export async function runAgentTurnWithFallback(params: {
replyToCurrent: true,
});
await params.opts.onBlockReply(noticePayload);
} else {
await params.opts?.onBlockReply?.({ text: "🧹 Compacting context..." });
}
}
const completed = evt.data?.completed === true;