From ed139d577b6e3daa2ecef5549e50d54d20506eec Mon Sep 17 00:00:00 2001 From: team 1 Date: Mon, 27 Apr 2026 08:44:00 +0200 Subject: [PATCH] fix sse HANGING tasks --- ...E_RUNNING_RECONNECT_FINALIZE_FIX_README.md | 42 +++++++++++++++++++ public/assets/js/base.js | 18 ++++---- src/Controller/AskSseController.php | 3 +- 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 RETRIEX_SSE_RUNNING_RECONNECT_FINALIZE_FIX_README.md diff --git a/RETRIEX_SSE_RUNNING_RECONNECT_FINALIZE_FIX_README.md b/RETRIEX_SSE_RUNNING_RECONNECT_FINALIZE_FIX_README.md new file mode 100644 index 0000000..383ee06 --- /dev/null +++ b/RETRIEX_SSE_RUNNING_RECONNECT_FINALIZE_FIX_README.md @@ -0,0 +1,42 @@ +# RetrieX SSE running reconnect / final cleanup fix + +Patch-only fix for the streamed answer UI after the job-based SSE flow. + +## Problem + +After introducing stream jobs, an EventSource reconnect to an already running job could receive: + +```text +retry: 15000 + +: duplicate-or-finished-stream + +event: done +data: [DONE] +``` + +Because `running` jobs were treated like harmless duplicate/finished streams, the browser could finalize an incomplete answer as if it had completed successfully. In addition, the final `done` cleanup still used the streaming cleanup path, which can keep the last `.think` block visible. + +## Changes + +- `running` duplicate/reconnect streams are no longer silently closed as successful `[DONE]` completions. +- Only already `completed` duplicate streams are silently closed with `done`. +- A reconnect to a still-running job now follows the existing explicit error path, so the UI can show a clear interruption message instead of silently accepting a partial answer. +- Final stream completion now removes all `.think` blocks and the loader class. + +## Changed files + +- `src/Controller/AskSseController.php` +- `public/assets/js/base.js` + +## After installing + +Run: + +```bash +php bin/console cache:clear +php bin/console mto:agent:config:validate +php bin/console mto:agent:regression:test +``` + +Also hard-refresh the browser or clear browser cache because `public/assets/js/base.js` is client-side JavaScript. diff --git a/public/assets/js/base.js b/public/assets/js/base.js index 6ce80e6..17d267e 100644 --- a/public/assets/js/base.js +++ b/public/assets/js/base.js @@ -232,16 +232,11 @@ document.addEventListener('DOMContentLoaded', () => { return false; } - function cleanupThinkSpans(container, final = false) { + function cleanupThinkSpans(container) { if (!container) { return; } - if (final) { - removeThinkSpansOnly(container); - return; - } - const thinkSpans = Array.from(container.querySelectorAll('.think')); if (thinkSpans.length === 0) { @@ -259,9 +254,9 @@ document.addEventListener('DOMContentLoaded', () => { removeThinkSpansOnly(container); } - function renderBubbleContent(bubble, raw, final = false) { + function renderBubbleContent(bubble, raw) { bubble.innerHTML = renderMarkdown(raw); - cleanupThinkSpans(bubble, final); + cleanupThinkSpans(bubble); enhanceChatLinks(bubble); scrollChatToBottom(); } @@ -338,8 +333,11 @@ document.addEventListener('DOMContentLoaded', () => { function finalizeStream(bubble, raw) { clearScheduledRender(); + bubble.innerHTML = renderMarkdown(raw); + removeThinkSpansOnly(bubble); bubble.classList.remove('loader'); - renderBubbleContent(bubble, raw, true); + enhanceChatLinks(bubble); + scrollChatToBottom(); } async function releaseStreamResources() { @@ -573,7 +571,7 @@ document.addEventListener('DOMContentLoaded', () => { if (raw.trim() !== '') { const formattedMessage = `${userMessage}`; raw += raw.trim() === '' ? formattedMessage : `\n\n${formattedMessage}`; - renderBubbleContent(bubble, raw, true); + renderBubbleContent(bubble, raw); } else { bubble.innerHTML = `${userMessage}`; enhanceChatLinks(bubble); diff --git a/src/Controller/AskSseController.php b/src/Controller/AskSseController.php index 924b002..6855877 100644 --- a/src/Controller/AskSseController.php +++ b/src/Controller/AskSseController.php @@ -526,8 +526,7 @@ final readonly class AskSseController $status = (string) ($claim['status'] ?? ''); - return $status === self::JOB_STATUS_RUNNING - || $status === self::JOB_STATUS_COMPLETED; + return $status === self::JOB_STATUS_COMPLETED; } /**