From 941653d1853b582699b67d649bcf4cd3615f4256 Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 27 Apr 2026 07:51:18 +0800 Subject: [PATCH] fix(agent): also drop the queue guard in doOnError path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../vip/mate/channel/web/ChatController.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java index b6f97a74..10960dd2 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java @@ -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); }