From 2fed808b9e716d9a44b34c7a073ec0d37374be05 Mon Sep 17 00:00:00 2001 From: centdix <40307056+centdix@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:21:49 +0200 Subject: [PATCH] fix(ai-chat): stop echoing app draft value in global chat write tool results (#9658) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit finishAppDraftWrite returned `item: result.item`, whose `value` is the entire app draft (every frontend file body and inline runnable). Each write_app_file / patch_app_file / write_app_runnable therefore re-sent the whole app back to the model; on a large app a few edits overflow the 200k context window. This restores #9530 (which removed the echo) — the DB-backed-draft refactor (#9601) reintroduced it by routing all app writes through this shared helper with `item:` re-added. Write results now return only `{ success, message }`, matching the flow write tools. Adds a regression test asserting the value is not echoed. Co-authored-by: Claude Opus 4.8 (1M context) --- .../copilot/chat/global/core.test.ts | 34 +++++++++++++++++++ .../components/copilot/chat/global/core.ts | 6 ++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/global/core.test.ts b/frontend/src/lib/components/copilot/chat/global/core.test.ts index 12442426f0..95e3195cc6 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.test.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.test.ts @@ -783,6 +783,40 @@ describe('global AI tools', () => { expect(getBackendDraft('raw_app', 'u/admin/live_app', { workspace: WORKSPACE })).toBeUndefined() }) + it('does not echo the app value back to the model on write', async () => { + const sentinel = 'SENTINEL_DO_NOT_ECHO_DEADBEEF' + seedBackendDraft( + 'raw_app', + '', + { + summary: 'Echo check', + files: { '/src/App.tsx': `export default function App() { return '${sentinel}' }` }, + runnables: {}, + data: { tables: [] } + }, + { workspace: WORKSPACE } + ) + UserDraft.setLiveEditorDraft({ + workspace: WORKSPACE, + itemKind: 'raw_app', + storagePath: '', + effectivePath: 'u/admin/echo_app' + }) + + const raw = await callGlobalTool('write_app_file', { + path: 'u/admin/echo_app', + file_path: '/src/New.tsx', + content: 'export default function New() { return null }' + }) + + const parsed = JSON.parse(raw) + expect(parsed.success).toBe(true) + expect(parsed.item).toBeUndefined() + // Neither the pre-existing file body nor the just-written one is resent. + expect(raw).not.toContain(sentinel) + expect(raw).not.toContain('function New') + }) + it('discards a draft without deleting the workspace item', async () => { await callGlobalTool('write_script', { path: 'f/scripts/discard-me', diff --git a/frontend/src/lib/components/copilot/chat/global/core.ts b/frontend/src/lib/components/copilot/chat/global/core.ts index 7058310637..e2f3501f1e 100644 --- a/frontend/src/lib/components/copilot/chat/global/core.ts +++ b/frontend/src/lib/components/copilot/chat/global/core.ts @@ -2399,13 +2399,13 @@ function draftWriteFailure(result: DraftPersistResult, ctx: WriteDraftCtx): stri function finishAppDraftWrite( result: DraftPersistResult, ctx: WriteDraftCtx, - onSaved: (item: WorkspaceItem) => { content: string; message: string } + onSaved: () => { content: string; message: string } ): string { const failure = draftWriteFailure(result, ctx) if (failure) return failure - const { content, message } = onSaved(result.item) + const { content, message } = onSaved() ctx.toolCallbacks.setToolStatus(ctx.toolId, { content, result: 'Saved as draft' }) - return JSON.stringify({ success: true, message, item: result.item }, null, 2) + return JSON.stringify({ success: true, message }, null, 2) } function finishDraftWrite(