diff --git a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts index 22a6b281f..e242e5bdd 100644 --- a/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +++ b/ui-tui/src/__tests__/createGatewayEventHandler.test.ts @@ -195,6 +195,45 @@ describe('createGatewayEventHandler', () => { expect((appended[0]?.text.match(/```diff/g) ?? []).length).toBe(1) }) + it('strips the CLI "┊ review diff" header from queued inline diffs', () => { + const appended: Msg[] = [] + const onEvent = createGatewayEventHandler(buildCtx(appended)) + const raw = ' \u001b[33m┊ review diff\u001b[0m\n--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new' + + onEvent({ + payload: { inline_diff: raw, summary: 'patched', tool_id: 'tool-1' }, + type: 'tool.complete' + } as any) + onEvent({ + payload: { text: 'done' }, + type: 'message.complete' + } as any) + + expect(appended).toHaveLength(1) + expect(appended[0]?.text).not.toContain('┊ review diff') + expect(appended[0]?.text).toContain('--- a/foo.ts') + }) + + it('suppresses inline_diff when assistant already wrote a diff fence', () => { + const appended: Msg[] = [] + const onEvent = createGatewayEventHandler(buildCtx(appended)) + const inlineDiff = '--- a/foo.ts\n+++ b/foo.ts\n@@\n-old\n+new' + const assistantText = 'Done. Clean swap:\n\n```diff\n-old\n+new\n```' + + onEvent({ + payload: { inline_diff: inlineDiff, summary: 'patched', tool_id: 'tool-1' }, + type: 'tool.complete' + } as any) + onEvent({ + payload: { text: assistantText }, + type: 'message.complete' + } as any) + + expect(appended).toHaveLength(1) + expect(appended[0]?.text).toBe(assistantText) + expect((appended[0]?.text.match(/```diff/g) ?? []).length).toBe(1) + }) + it('keeps tool trail terse when inline_diff is present', () => { const appended: Msg[] = [] const onEvent = createGatewayEventHandler(buildCtx(appended)) diff --git a/ui-tui/src/app/turnController.ts b/ui-tui/src/app/turnController.ts index 005eed4bc..bf9d2926c 100644 --- a/ui-tui/src/app/turnController.ts +++ b/ui-tui/src/app/turnController.ts @@ -185,7 +185,13 @@ class TurnController { } queueInlineDiff(diffText: string) { - const text = diffText.trim() + // Strip CLI chrome the gateway emits before the unified diff (e.g. a + // leading "┊ review diff" header written by `_emit_inline_diff` for the + // terminal printer). That header only makes sense as stdout dressing, + // not inside a markdown ```diff block. + const text = diffText + .replace(/^\s*┊[^\n]*\n?/, '') + .trim() if (!text || this.pendingInlineDiffs.includes(text)) { return @@ -239,7 +245,13 @@ class TurnController { const rawText = (payload.rendered ?? payload.text ?? this.bufRef).trimStart() const split = splitReasoning(rawText) const finalText = split.text - const remainingInlineDiffs = this.pendingInlineDiffs.filter(diff => !finalText.includes(diff)) + // Skip appending if the assistant already narrated the diff inside a + // markdown fence of its own — otherwise we render two stacked diff + // blocks for the same edit. + const assistantAlreadyHasDiff = /```(?:diff|patch)\b/i.test(finalText) + const remainingInlineDiffs = assistantAlreadyHasDiff + ? [] + : this.pendingInlineDiffs.filter(diff => !finalText.includes(diff)) const inlineDiffBlock = remainingInlineDiffs.length ? `\`\`\`diff\n${remainingInlineDiffs.join('\n\n')}\n\`\`\`` : ''