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:
@@ -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 }] : [],
|
||||
],
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -461,10 +461,17 @@ describe('the run_code dispatch bridge', () => {
|
||||
isError: false,
|
||||
meta: { logs: [{ source: 'console', level: 'log', text: 'printed' }], dispatches: 1 },
|
||||
})
|
||||
expect(view).toEqual({ card: 'generic', title: 'Run code (1 tool call)', content: [{ type: 'text', text: 'printed' }] })
|
||||
// Plural title, and no content when the program printed nothing.
|
||||
// The result re-carries the fenced program before the output: the ACP
|
||||
// update's content REPLACES the pending card's, so omitting it would
|
||||
// wipe the code from the card the moment the run completes.
|
||||
expect(view).toEqual({
|
||||
card: 'generic',
|
||||
title: 'Run code (1 tool call)',
|
||||
content: [{ type: 'text', text: '```ts\nreturn 1\n```' }, { type: 'text', text: 'printed' }],
|
||||
})
|
||||
// Plural title, and the program alone when it printed nothing.
|
||||
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { logs: [], dispatches: 2 } }))
|
||||
.toEqual({ card: 'generic', title: 'Run code (2 tool calls)' })
|
||||
.toEqual({ card: 'generic', title: 'Run code (2 tool calls)', content: [{ type: 'text', text: '```ts\nx\n```' }] })
|
||||
// Replay with an unrecognizable meta falls back to the generic rendering.
|
||||
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false, meta: { other: true } })).toBeUndefined()
|
||||
expect(tool.presentResult?.({ code: 'x' }, { content: [], isError: false })).toBeUndefined()
|
||||
|
||||
Reference in New Issue
Block a user