Skip to content

fix(desktop): preserve pre-tool-call text parts on message completion - #64492

Closed
isukennedy wants to merge 2 commits into
NousResearch:mainfrom
isukennedy:fix/desktop-preserve-pre-tool-text
Closed

fix(desktop): preserve pre-tool-call text parts on message completion#64492
isukennedy wants to merge 2 commits into
NousResearch:mainfrom
isukennedy:fix/desktop-preserve-pre-tool-text

Conversation

@isukennedy

Copy link
Copy Markdown

Problem

When the model streams text, calls tools, then streams more text, the desktop app drops the text that was streamed before the tool calls. Only the final API response's content survives.

Example: Model says "Here's what I found:" -> calls search_files -> says "The results show X."

What the user sees after completion: just "The results show X." — the pre-tool-call narration is gone.

Root cause

replaceTextPart() in completeAssistantMessage unconditionally filters out ALL text parts and replaces them with a single text part containing finalText from message.complete. But finalText only contains the last API call's content — text from earlier API calls in the tool-calling loop was streamed via message.delta and flushed to text parts, but those get nuked on completion.

Fix

Find the last tool-call part index. Text parts before it are preserved (from earlier API calls). Only text parts after the last tool-call are replaced (they're from the final API call, covered by finalText).

Files changed

  • apps/desktop/src/app/session/hooks/use-message-stream/index.tsreplaceTextPart() in completeAssistantMessage()

The replaceTextPart function in completeAssistantMessage was dropping ALL
text parts and replacing them with the final_response from message.complete.
But final_response only contains the last API call's content — text streamed
before tool calls (from earlier API calls in the tool-calling loop) was
silently discarded.

Fix: find the last tool-call part index and preserve text parts that appear
before it. Only replace text parts after the last tool-call (which come from
the final API call and are covered by finalText).
@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have labels Jul 14, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating this to the completion reconciliation path. The premise is confirmed on current main: replaceTextPart() removes every text part at apps/desktop/src/app/session/hooks/use-message-stream/index.ts:359-377, while tool events flush preceding deltas before inserting the tool row at :294-328.

Problems

  • The PR changes only apps/desktop/src/app/session/hooks/use-message-stream/index.ts and adds no regression coverage. Existing hook-level event harnesses in apps/desktop/src/app/session/hooks/use-message-stream/todo-cleanup.test.tsx:17-52 provide the appropriate integration boundary.

Suggested changes

  • Add a test for message.delta before a tool, tool.start/tool.complete, another delta, and message.complete. Assert the pre-tool narration and tool row survive while the trailing streamed text is reconciled to the completion payload.

The implementation’s last-tool-call boundary is otherwise aligned with the current stream ordering. This is an automated hermes-sweeper review.

const dedupeReference = normalize(visibleFinalText)

const kept = parts.filter(part => {
// Find the last tool-call part index. Text parts before it came from

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a hook-level regression test for pre-tool message.delta text followed by a tool event and message.complete; this boundary is the behavior the change must preserve.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
@ethernet8023

Copy link
Copy Markdown
Collaborator

closed by #65919

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants