fix: keep the program on the COMPLETED run_code card (agent review follow-up)

The previous commit put the fenced program only on the pending card —
but an ACP tool_call_update's content REPLACES the card content (Zed
truncates to the new list, crates/acp_thread update_fields), so the
code vanished the moment the run completed and was effectively never
visible. presentResult now re-carries the fenced program before the
captured output via a shared fencedProgram helper; the completed card
body is program + output, rendered by Zed as syntax-highlighted
markdown behind the card disclosure. Goldens re-recorded (filtered
this time: DSH_SNAPSHOT=record vitest -u -t mode-turn); unit test pins
the two-block result content.
This commit is contained in:
Tianyi Cui
2026-07-09 22:04:52 +08:00
parent 387f19c7f6
commit f505776eee
6 changed files with 422 additions and 383 deletions
+23 -7
View File
@@ -135,6 +135,15 @@ function asRunCodeMeta(meta: unknown): RunCodeMeta | undefined {
return m as unknown as RunCodeMeta
}
/**
* Render a program as the markdown block the tool-call cards carry.
* @param code - the program text.
* @returns the ts-fenced markdown block.
*/
function fencedProgram(code: string): string {
return `\`\`\`ts\n${code}\n\`\`\``
}
/**
* Build the `run_code` {@link ToolDefinition}: one required `code` parameter,
* executed through the dispatch bridge described in the module doc. The
@@ -289,25 +298,32 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
exec.signal?.removeEventListener('abort', onOuterAbort)
}
},
// The program IS the call: surface it as an always-visible fenced block in
// the card body (rawInput alone lands in detail/expanded views many
// clients never open). Fence collisions are impossible to break rendering
// — a backtick run inside the program at worst ends the block early.
// The program IS the call: surface it as a fenced block in the card body
// (rawInput alone lands in detail/expanded views many clients never
// open). Fence collisions are impossible to break rendering — a backtick
// run inside the program at worst ends the block early.
presentCall: args => ({
card: 'generic',
title: 'Run code',
kind: 'execute',
rawInput: args.code,
content: [{ type: 'text', text: `\`\`\`ts\n${args.code}\n\`\`\`` }],
content: [{ type: 'text', text: fencedProgram(args.code) }],
}),
presentResult: (_args, result) => {
// The result re-carries the program BEFORE the captured output: an ACP
// tool_call_update's `content` REPLACES the pending card's (clients
// truncate to the new list), so a result without the program would wipe
// it the moment the run completes.
presentResult: (args, result) => {
const meta = asRunCodeMeta(result.meta)
if (!meta) return undefined
const output = meta.logs.map(entry => entry.text).join('\n')
return {
card: 'generic',
title: `Run code (${meta.dispatches} tool call${meta.dispatches === 1 ? '' : 's'})`,
...output.length > 0 ? { content: [{ type: 'text', text: output }] } : {},
content: [
{ type: 'text', text: fencedProgram(args.code) },
...output.length > 0 ? [{ type: 'text' as const, text: output }] : [],
],
}
},
})