fix(desktop): strip controller-injected prefixes from chat display (#3720)#3738
Closed
ashishexee wants to merge 1 commit into
Closed
fix(desktop): strip controller-injected prefixes from chat display (#3720)#3738ashishexee wants to merge 1 commit into
ashishexee wants to merge 1 commit into
Conversation
SuMuxi66
pushed a commit
to SuMuxi66/DeepSeek-Reasonix
that referenced
this pull request
Jun 10, 2026
…sengine#3720) Strip controller-injected prefixes ([Plan mode] marker, <memory-update>, <background-jobs>) and synthetic Role:user messages from the chat display so agent-mode internals no longer leak into the desktop/TUI transcript. Closes esengine#3738, Closes esengine#3720.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3720
Problem
Agent mode's injected system prompt (
[Plan mode — read-only...]) and other controller-injected prefixes leak into the desktop chat UI as visible user messages. This affects:.display.jsonsidecar exists, sosessionDisplayResolver()falls back to returning raw composed content withPlanModeMarker,<memory-update>, and<background-jobs>blocks intact.planApprovedMessage, stream-recovery messages, readiness-retry messages, and executor-handoff messages are stored asRole: "user"but should never appear in the chat UI. The frontend only filtersrole === "system"— synthetic messages pass through.The CLI does NOT have this leak for
PlanModeMarker(it already strips it inreplaySectionsFor), but it DOES leak<memory-update>and<background-jobs>blocks.Solution
StripComposePrefixes (new function in
internal/control/input.go)Strips all controller-injected prefixes from a composed user message:
<memory-update>…</memory-update>blocks (prepended byCompose()when memory is queued)<background-jobs>…</background-jobs>blocks (prepended byCompose()for job completions)PlanModeMarker + "\n\n"prefix (prepended when plan mode is active)Used as the fallback in
sessionDisplayResolver()when no.display.jsonsidecar exists — so old sessions display correctly without backfill.IsSyntheticUserMessage (new function in
internal/control/input.go)Detects known synthetic user messages by exact match (
planApprovedMessage) or specific prefixes:"Plan approved — plan mode is off""Host final-answer readiness check failed""You are already in the executor phase""The previous assistant response was interrupted while a tool call"/…during streaming"/…before visible""The previous assistant response finished without any visible answer"Prefixes are specific enough to avoid false positives — a user typing "my response was interrupted by VPN" will NOT be filtered. A sync comment on the
syntheticPrefixesvar notes it must be kept in sync with the constants incontroller.goandagent.go.Integration points
desktop/sessions.go:sessionDisplayResolver()— fallback changed fromreturn contenttoreturn control.StripComposePrefixes(content)— fixes old-session leakdesktop/app.go:historyMessages()— addedif control.IsSyntheticUserMessage(content) { continue }— filters synthetic messages from historyinternal/cli/chat_tui.go:replaySectionsFor()— changedstrings.TrimPrefix(m.Content, PlanModeMarker+"\n\n")tocontrol.StripComposePrefixes(m.Content)— also strips memory/job blocks in CLI session replayWhy this approach
Syntheticfield toprovider.MessageIsSyntheticUserMessageis still needed as a fallback for those.This fix handles both old sessions (via
StripComposePrefixesfallback) AND new sessions (viaIsSyntheticUserMessagefilter), with no session format changes.Testing
18 new test cases:
TestStripComposePrefixes— 8 cases: plain message, plan marker stripped, marker alone, memory block, jobs block, memory+marker, empty after strip, memory-onlyTestIsSyntheticUserMessage— 10 cases: plan approved, 3 stream recovery variants, empty final, readiness retry, executor handoff, regular message (false), plan marker (false), interrupted-before-visible variant, and a false-positive test confirming a user typing "my response was interrupted by VPN" is NOT filtered