diff --git a/extensions/mattermost/src/mattermost/monitor.ts b/extensions/mattermost/src/mattermost/monitor.ts index d374ba60dc8..a4c1f43d7a5 100644 --- a/extensions/mattermost/src/mattermost/monitor.ts +++ b/extensions/mattermost/src/mattermost/monitor.ts @@ -1427,9 +1427,17 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} const flushPendingPatch = async () => { stopPatchInterval(); if (!blockStreamingClient) return; + // Wait for any in-flight interval tick to settle before flushing. + // Without this, an interval tick that set lastSentText synchronously but hasn't + // completed the async send yet would cause flushPendingPatch to exit early + // (text === lastSentText guard), leaving streamMessageId null and causing + // final delivery to fall through to a new post instead of patching in place. + const deadline = Date.now() + 2000; + while (patchSending && Date.now() < deadline) { + await new Promise((r) => setTimeout(r, 20)); + } const text = pendingPatchText; if (!text || text === lastSentText) return; - lastSentText = text; if (!streamMessageId) { try { const result = await sendMessageMattermost(to, text, { @@ -1437,6 +1445,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} replyToId: effectiveReplyToId, }); streamMessageId = result.messageId; + lastSentText = text; runtime.log?.(`stream-patch started ${streamMessageId}`); } catch (err) { logVerboseMessage(`mattermost stream-patch flush send failed: ${String(err)}`); @@ -1447,6 +1456,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} postId: streamMessageId, message: text, }); + lastSentText = text; runtime.log?.(`stream-patch flushed ${streamMessageId}`); } catch (err) { logVerboseMessage(`mattermost stream-patch flush failed: ${String(err)}`); @@ -1461,7 +1471,6 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} patchInterval = setInterval(() => { const text = pendingPatchText; if (!text || text === lastSentText || patchSending) return; - lastSentText = text; patchSending = true; void (async () => { try { @@ -1472,6 +1481,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} replyToId: effectiveReplyToId, }); streamMessageId = result.messageId; + lastSentText = text; runtime.log?.(`stream-patch started ${streamMessageId}`); } catch (err) { logVerboseMessage(`mattermost stream-patch send failed: ${String(err)}`); @@ -1482,6 +1492,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} postId: streamMessageId, message: text, }); + lastSentText = text; runtime.log?.(`stream-patch edited ${streamMessageId}`); } catch (err) { logVerboseMessage(`mattermost stream-patch edit failed: ${String(err)}`); @@ -1546,7 +1557,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {} }); return; } + // Successful final patch: reset all streaming state. streamMessageId = null; + pendingPatchText = ""; + lastSentText = ""; + patchSending = false; return; }