From a16e5c4cdbfa4c3a0c37d841bce18a762ed26891 Mon Sep 17 00:00:00 2001 From: Saul-Gomez-J Date: Tue, 28 Apr 2026 13:27:58 +0200 Subject: [PATCH 1/2] fix(ai): scope tool output sanitizer to legacy MCP-UI payloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the guard removed in PR #256 so the cleanOutput block only runs on outputs that actually need MCP-UI sanitization (uiResources, images, or content[] with image entries). Without the guard, any non-canonical plain-object tool result without `text`/`content[]`/`structuredContent` keys was being replaced with `[Widget rendered]`, breaking every coding tool (bash, read, write, edit, grep, find, ls, task tools) on multi-turn replays — the model received the placeholder instead of the actual command output. Co-Authored-By: Claude Opus 4.7 --- .../ai/__tests__/toolMessageSanitizer.test.ts | 58 +++++++++++++++++++ src/main/services/ai/toolMessageSanitizer.ts | 10 +++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main/services/ai/__tests__/toolMessageSanitizer.test.ts b/src/main/services/ai/__tests__/toolMessageSanitizer.test.ts index 5d68548..04399b6 100644 --- a/src/main/services/ai/__tests__/toolMessageSanitizer.test.ts +++ b/src/main/services/ai/__tests__/toolMessageSanitizer.test.ts @@ -378,4 +378,62 @@ describe('sanitizeMessagesForModel', () => { expect(part.output.content).toEqual([{ type: 'text', text: 'hi' }]); expect(part.output.uiResources).toBeUndefined(); }); + + it('preserves coding tool plain-object outputs (bash) untouched', () => { + const output = { + status: 'success', + exitCode: 0, + output: 'hello\nworld', + truncated: false, + }; + + const messages: UIMessage[] = [ + makeAssistantMessage([ + makeToolPart({ state: 'output-available', output }), + ]), + ]; + + const result = sanitizeMessagesForModel(messages); + const part = result[0].parts[0] as any; + + expect(part.output).toEqual(output); + }); + + it('preserves read-tool plain object with content as string', () => { + const output = { + success: true, + content: 'string contents', + totalLines: 50, + }; + + const messages: UIMessage[] = [ + makeAssistantMessage([ + makeToolPart({ state: 'output-available', output }), + ]), + ]; + + const result = sanitizeMessagesForModel(messages); + const part = result[0].parts[0] as any; + + expect(part.output).toEqual(output); + }); + + it('preserves grep tool empty-match output', () => { + const output = { + success: true, + matches: 0, + message: 'No matches found', + }; + + const messages: UIMessage[] = [ + makeAssistantMessage([ + makeToolPart({ state: 'output-available', output }), + ]), + ]; + + const result = sanitizeMessagesForModel(messages); + const part = result[0].parts[0] as any; + + expect(part.output).toEqual(output); + }); }); diff --git a/src/main/services/ai/toolMessageSanitizer.ts b/src/main/services/ai/toolMessageSanitizer.ts index b8aefe2..53d1d34 100644 --- a/src/main/services/ai/toolMessageSanitizer.ts +++ b/src/main/services/ai/toolMessageSanitizer.ts @@ -137,7 +137,15 @@ export function sanitizeMessagesForModel(messages: UIMessage[]): UIMessage[] { return part; } - if (output && typeof output === 'object') { + const isLegacyMcpUiOutput = + output && typeof output === 'object' && ( + 'uiResources' in output || + 'images' in output || + (Array.isArray((output as any).content) && + (output as any).content.some((c: any) => c?.type === 'image')) + ); + + if (isLegacyMcpUiOutput) { const cleanOutput: Record = {}; if ((output as any).structuredContent) { From 2820635960f0a2b0e6a5e4734eaf712829d4190d Mon Sep 17 00:00:00 2001 From: Saul-Gomez-J Date: Tue, 28 Apr 2026 13:30:00 +0200 Subject: [PATCH 2/2] chore(release): v1.8.2 Promote to 1.8.2 stable. Includes a follow-up fix to scope the legacy MCP-UI tool output sanitizer so it no longer touches modern canonical payloads. Co-Authored-By: Claude Opus 4.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9938815..3714dc1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "levante", - "version": "1.8.1-beta.3", + "version": "1.8.2", "description": "A friendly, private desktop chat app with AI and MCP integration", "main": ".vite/build/main.js", "homepage": "https://github.com/minte-community/levante",