fix(tui): render inline diffs inside assistant completion

Follow-up for #13729: segment-level system artifacts still looked detached in real flow.\n\nInstead of appending inline_diff as a standalone segment/system row, queue sanitized diffs during tool.complete and append them as a fenced diff block to the assistant completion text on message.complete. This keeps the diff in the same message flow as the assistant response.
This commit is contained in:
Brooklyn Nicholson 2026-04-21 19:02:53 -05:00
parent bddf0cd61e
commit 31b3b09ea4
3 changed files with 30 additions and 40 deletions

View file

@ -39,6 +39,7 @@ class TurnController {
bufRef = ''
interrupted = false
lastStatusNote = ''
pendingInlineDiffs: string[] = []
persistedToolLabels = new Set<string>()
protocolWarned = false
reasoningText = ''
@ -76,6 +77,7 @@ class TurnController {
this.activeTools = []
this.streamTimer = clear(this.streamTimer)
this.bufRef = ''
this.pendingInlineDiffs = []
this.pendingSegmentTools = []
this.segmentMessages = []
@ -182,26 +184,14 @@ class TurnController {
}, REASONING_PULSE_MS)
}
/**
* Append an inline artifact (e.g. tool-complete inline diff) to the active
* turn's segment stream. Routing through `historyItems` via `sys()` lands
* the artifact above the currently-streaming assistant bubble; adding it
* here keeps the paint order aligned with the order the gateway emitted.
*/
appendSegmentMessage(msg: Msg) {
this.flushStreamingSegment()
queueInlineDiff(diffText: string) {
const text = diffText.trim()
if (this.pendingSegmentTools.length) {
this.segmentMessages = [
...this.segmentMessages,
{ kind: 'trail', role: 'system', text: '', tools: this.pendingSegmentTools }
]
this.pendingSegmentTools = []
patchTurnState({ streamPendingTools: [] })
if (!text) {
return
}
this.segmentMessages = [...this.segmentMessages, msg]
patchTurnState({ streamSegments: this.segmentMessages })
this.pendingInlineDiffs = [...this.pendingInlineDiffs, text]
}
pushActivity(text: string, tone: ActivityItem['tone'] = 'info', replaceLabel?: string) {
@ -238,6 +228,7 @@ class TurnController {
this.idle()
this.clearReasoning()
this.clearStatusTimer()
this.pendingInlineDiffs = []
this.pendingSegmentTools = []
this.segmentMessages = []
this.turnTools = []
@ -248,6 +239,10 @@ class TurnController {
const rawText = (payload.rendered ?? payload.text ?? this.bufRef).trimStart()
const split = splitReasoning(rawText)
const finalText = split.text
const inlineDiffBlock = this.pendingInlineDiffs.length
? `\`\`\`diff\n${this.pendingInlineDiffs.join('\n\n')}\n\`\`\``
: ''
const mergedText = [finalText, inlineDiffBlock].filter(Boolean).join('\n\n')
const existingReasoning = this.reasoningText.trim() || String(payload.reasoning ?? '').trim()
const savedReasoning = [existingReasoning, existingReasoning ? '' : split.reasoning].filter(Boolean).join('\n\n')
const savedReasoningTokens = savedReasoning ? estimateTokensRough(savedReasoning) : 0
@ -255,10 +250,10 @@ class TurnController {
const tools = this.pendingSegmentTools
const finalMessages = [...this.segmentMessages]
if (finalText) {
if (mergedText) {
finalMessages.push({
role: 'assistant',
text: finalText,
text: mergedText,
thinking: savedReasoning || undefined,
thinkingTokens: savedReasoning ? savedReasoningTokens : undefined,
toolTokens: savedToolTokens || undefined,
@ -275,7 +270,7 @@ class TurnController {
this.bufRef = ''
patchTurnState({ activity: [], outcome: '' })
return { finalMessages, finalText, wasInterrupted }
return { finalMessages, finalText: mergedText, wasInterrupted }
}
recordMessageDelta({ rendered, text }: { rendered?: string; text?: string }) {
@ -381,6 +376,7 @@ class TurnController {
this.bufRef = ''
this.interrupted = false
this.lastStatusNote = ''
this.pendingInlineDiffs = []
this.pendingSegmentTools = []
this.protocolWarned = false
this.segmentMessages = []
@ -426,6 +422,7 @@ class TurnController {
this.endReasoningPhase()
this.clearReasoning()
this.activeTools = []
this.pendingInlineDiffs = []
this.turnTools = []
this.toolTokenAcc = 0
this.persistedToolLabels.clear()