fix: the run_code program IS the execute-card title (root cause: Zed shows nothing else)

Systematic trace through Zed (crates/agent_ui thread_view.rs +
crates/acp_thread): kind:execute routes a tool call onto the
terminal-card layout, whose header (render_collapsible_command) has NO
disclosure toggle, whose body content renders only when is_open — a
flag only a real terminal entity can ever set — and which suppresses
the Raw Input view outright. Every prior attempt (rawInput, pending
content, completed content) targeted slots that layout structurally
never renders; the one slot it always shows is the TITLE, which said
"Run code". codex-acp confirms the idiom: execute cards are titled
with the command itself.

presentCall now titles the card with the program (rawInput kept as the
canonical input slot); presentResult omits the title — an update
replaces only provided fields, so the program header persists — and
carries the captured output as content. Goldens re-recorded; the unit
test pins title-carries-program on both frames.
This commit is contained in:
Tianyi Cui
2026-07-09 22:41:57 +08:00
parent f505776eee
commit 30bc7f6a1d
6 changed files with 391 additions and 393 deletions
+13 -25
View File
@@ -135,15 +135,6 @@ 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
@@ -298,32 +289,29 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
exec.signal?.removeEventListener('abort', onOuterAbort)
}
},
// 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.
// The program IS the title, the way command tools title their cards with
// the command: an execute-card's title is the one slot an ACP client
// always shows (Zed's execute cards render no body content and no raw
// input without a real terminal attached), so anywhere else the code
// would be invisible. Multi-line titles are the execute-card idiom —
// capable clients render them whole; others truncate to the first line
// and still hold the full program in rawInput.
presentCall: args => ({
card: 'generic',
title: 'Run code',
title: args.code,
kind: 'execute',
rawInput: args.code,
content: [{ type: 'text', text: fencedProgram(args.code) }],
}),
// 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) => {
// Title omitted on the result: an update replaces only the fields it
// carries, so the pending card's program title persists through
// completion; the captured output rides as body content.
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'})`,
content: [
{ type: 'text', text: fencedProgram(args.code) },
...output.length > 0 ? [{ type: 'text' as const, text: output }] : [],
],
...output.length > 0 ? { content: [{ type: 'text' as const, text: output }] } : {},
}
},
})