fix(agent): also drop the queue guard in doOnError path

Same bug as the prior queue-drop fix in doOnComplete, but in the
sister branch that fires when the agent's reactive stream errors
out (CancellationException from a user stop). The guard

  cr.queuedInput() != null && !(isUserStop && !isInterruptFollowup)

mis-classified "user stopped, no interrupt-with-followup, but a
message is in the queue" as an explicit abort and silently dropped
the freshly-typed follow-up.

The frontend's enqueue path never sets interruptType — it just
calls requestStop + offers to messageQueue. Whoever puts a message
in the queue means it; just run it. Aligns with doOnComplete and
the four other queue-launch sites in this controller.
This commit is contained in:
matevip 2026-04-27 07:51:18 +08:00
parent fcdb3fc15e
commit 941653d185

View File

@ -711,17 +711,21 @@ public class ChatController {
log.info("SSE doOnError cleanup: conversationId={}, allDone={}, isInterruptFollowup={}, hasQueued={}",
conversationId, cr.allDone(), isInterruptFollowup, cr.queuedInput() != null);
if (cr.allDone()) {
// 修复非用户主动停止时也消费排队消息
// isUserStop && !isInterruptFollowup = 用户点了 Stop不应续跑
boolean userExplicitStop = isUserStop && !isInterruptFollowup;
if (cr.queuedInput() != null && !userExplicitStop) {
// RFC follow-up (2026-04-27): the previous guard
// cr.queuedInput()!=null && !(isUserStop && !isInterruptFollowup)
// tried to suppress continuation when the user "explicitly
// stopped" without an interrupt-with-followup. But the
// frontend's enqueue path doesn't set interruptType it
// just calls requestStop + offers to messageQueue. From the
// server's POV that's "isUserStop=true, isInterruptFollowup=
// false, queue has content", which the guard mis-classified
// as "abort" and silently dropped the user's freshly-typed
// follow-up. Whoever puts a message in messageQueue means it
// just run it. Aligns with doOnComplete and the 4 other
// queue-launch sites in this controller.
if (cr.queuedInput() != null) {
startQueuedMessage(conversationId, emitter, emitterDone, cr.queuedInput(), username);
} else {
// 即使不续跑如果有排队消息也要持久化用户消息防丢失幂等
if (cr.queuedInput() != null && !cr.queuedInput().persisted()) {
conversationService.saveMessage(conversationId, "user",
cr.queuedInput().message(), null, "queued");
}
conversationService.updateStreamStatus(conversationId, "idle");
completeEmitterQuietly(emitter, emitterDone);
}