Merge remote-tracking branch 'origin/master' into feat/web-diff-card

# Conflicts:
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/chat/GenericToolCard.tsx
#	packages/client/ui-conversation/src/client/chat/ToolRow.tsx
#	packages/client/ui-conversation/src/client/skeleton/DetailsPanel.tsx
#	packages/client/ui-primitives/README.i18n.yaml
#	packages/client/ui-primitives/src/index.ts
This commit is contained in:
Chinesezjc
2026-07-31 04:22:57 +08:00
782 changed files with 20829 additions and 3372 deletions

View File

@@ -105,10 +105,10 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
await ctx.plugin(UserInteractionService)
await ctx.plugin(TuiPromptService)
const catalog = options.catalog ?? {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [
{ provider: 'deepseek', id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash' },
{ provider: 'deepseek', id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro' },
{ provider: 'deepseek-official', id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash' },
{ provider: 'deepseek-official', id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro' },
],
}
ctx.provide('tokenMeter', {
@@ -194,7 +194,7 @@ export async function createTuiTestHarness<TerminalType extends Terminal, Exit e
const cancelled: AgentCancelCause[] = []
const agent: FakeAgent = {
id: sessionId,
options: options.agentOptions ?? { provider: 'deepseek', model: 'deepseek-v4-flash' },
options: options.agentOptions ?? { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
session,
status: options.status ?? 'idle',
get acceptsNextStep() {

View File

@@ -152,8 +152,8 @@ describe('TUI prompt values', () => {
describe('TUI prompt templates', () => {
it('interpolates values and removes separators around unavailable values', () => {
const tokens = parseTuiPromptTemplate('${cwd} ${git/worktree} :: ${missing} ${model}')
const values = new Map([['cwd', '/work'], ['model', 'deepseek']])
expect(renderTuiPromptTemplate(tokens, name => values.get(name))).toBe('/work :: deepseek')
const values = new Map([['cwd', '/work'], ['model', 'deepseek-official']])
expect(renderTuiPromptTemplate(tokens, name => values.get(name))).toBe('/work :: deepseek-official')
})
it('keeps a trailing literal after the last value', () => {

View File

@@ -36,9 +36,9 @@ buffer
14| " │ │ "
style 8-8 fg=bright-magenta
style 83-83 fg=bright-magenta
15| " │ → deepseek/deepseek-v4-pro DeepSeek V4 Pro │ "
15| " │ → deepseek-official/deepseek-v4- DeepSeek V4 Pro │ "
style 8-8 fg=bright-magenta
style 10-58 fg=bright-magenta inverse
style 10-41 fg=bright-magenta inverse
style 83-83 fg=bright-magenta
16| " │ │ "
style 8-8 fg=bright-magenta

View File

@@ -36,13 +36,13 @@ buffer
14| " │ │ "
style 8-8 fg=bright-magenta
style 83-83 fg=bright-magenta
15| " │ → deepseek/deepseek-v4-flash DeepSeek V4 Flash — current │ "
15| " │ → deepseek-official/deepseek-v4- DeepSeek V4 Flash — current │ "
style 8-8 fg=bright-magenta
style 10-70 fg=bright-magenta inverse
style 10-41 fg=bright-magenta inverse
style 83-83 fg=bright-magenta
16| " │ deepseek/deepseek-v4-pro DeepSeek V4 Pro │ "
16| " │ deepseek-official/deepseek-v4- DeepSeek V4 Pro │ "
style 8-8 fg=bright-magenta
style 36-58 dim
style 42-58 dim
style 83-83 fg=bright-magenta
17| " │ │ "
style 8-8 fg=bright-magenta

View File

@@ -16,8 +16,8 @@ buffer
5| "Model wait 0.0s "
style 0-14 dim
6| <blank>
7| "Model selected: deepseek/deepseek-v4-pro. New steps will use it. "
style 0-63 dim
7| "Model selected: deepseek-official/deepseek-v4-pro. New steps will use it. "
style 0-72 dim
8| <blank>
9| "/workspace/project (tui-staging) deepseek-v4-pro ↑0 ↓0 0% context"
style 0-17 fg=bright-magenta bold

View File

@@ -31,15 +31,15 @@ buffer
13| " unavailable: current session "
style 2-31 fg=yellow
14| " Other workspace work "
15| " 2024-02-02T00:00:08.000Z · turn 1: completed · deepseek/deepseek-v4-pro "
style 2-74 dim
15| " 2024-02-02T00:00:08.000Z · turn 1: completed · deepseek-official/deepseek-v4-pro "
style 2-83 dim
16| " persisted · elsewhere-session "
style 2-32 dim
17| " workspace /workspace/other "
style 2-29 dim
18| " Resume selector design "
19| " 2024-01-01T00:00:08.000Z · turn 1: completed · deepseek/deepseek-v4-pro "
style 2-74 dim
19| " 2024-01-01T00:00:08.000Z · turn 1: completed · deepseek-official/deepseek-v4-pro "
style 2-83 dim
20| " persisted · earlier-session "
style 2-30 dim
21| " workspace /workspace/project "

View File

@@ -29,8 +29,8 @@ buffer
12| " unavailable: current session "
style 2-31 fg=yellow
13| " Resume selector design "
14| " 2024-01-01T00:00:08.000Z · turn 1: completed · deepseek/deepseek-v4-pro "
style 2-74 dim
14| " 2024-01-01T00:00:08.000Z · turn 1: completed · deepseek-official/deepseek-v4-pro "
style 2-83 dim
15| " persisted · earlier-session "
style 2-30 dim
16| " "

View File

@@ -1,7 +1,7 @@
terminal 56x36 buffer=normal length=44 base=8 viewport=8
terminal 56x36 buffer=normal length=45 base=9 viewport=9
lifecycle started=1 stopped=0 progress=inactive
title "Inspect session diagnostics — DSH snapshot"
cursor hidden column=7 viewportRow=35 bufferRow=43
cursor hidden column=7 viewportRow=35 bufferRow=44
buffer
0| " DEEPSEEK HARNESS"
style 1-8 fg=bright-magenta bold
@@ -37,84 +37,87 @@ buffer
style 0-0 dim
style 3-12 dim
style 55-55 dim
15| "│ Model: deepseek/deepseek-v4-pro (effort │"
style 0-0 dim
style 3-12 dim
style 40-55 dim
16| "│ default; reasoning blocks shown) │"
style 0-0 dim
style 15-46 dim
style 55-55 dim
17| "│ │"
style 0-0 dim
style 55-55 dim
18| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 │"
15| "│ Model: deepseek-official/deepseek-v4-pro │"
style 0-0 dim
style 3-12 dim
style 55-55 dim
19| "│ tool call │"
16| "│ (effort default; reasoning blocks │"
style 0-0 dim
style 15-55 dim
17| "│ shown) │"
style 0-0 dim
style 15-20 dim
style 55-55 dim
18| "│ │"
style 0-0 dim
style 55-55 dim
20| "│ │"
style 0-0 dim
style 55-55 dim
21| "│ Tokens: 1,250 input + 340 output │"
19| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 │"
style 0-0 dim
style 3-12 dim
style 55-55 dim
22| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read │"
20| "│ tool call │"
style 0-0 dim
style 55-55 dim
21| "│ │"
style 0-0 dim
style 55-55 dim
22| "│ Tokens: 1,250 input + 340 output │"
style 0-0 dim
style 3-12 dim
style 55-55 dim
23| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read │"
style 0-0 dim
style 3-12 dim
style 15-15 dim
style 16-26 fg=bright-magenta
style 27-32 dim
style 55-55 dim
23| "│ + 250 write) │"
24| "│ + 250 write) │"
style 0-0 dim
style 55-55 dim
24| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / │"
25| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / │"
style 0-0 dim
style 3-12 dim
style 15-15 dim
style 16-20 fg=bright-magenta
style 21-32 dim
style 55-55 dim
25| "│ 128,000) │"
26| "│ 128,000) │"
style 0-0 dim
style 55-55 dim
26| "│ │"
27| "│ │"
style 0-0 dim
style 55-55 dim
27| "│ Created: 2026-07-22 09:10:11 UTC │"
28| "│ Created: 2026-07-22 09:10:11 UTC │"
style 0-0 dim
style 3-12 dim
style 55-55 dim
28| "│ Active: 2026-07-22 09:10:11 UTC │"
29| "│ Active: 2026-07-22 09:10:11 UTC │"
style 0-0 dim
style 3-12 dim
style 55-55 dim
29| "╰──────────────────────────────────────────────────────╯"
30| "╰──────────────────────────────────────────────────────╯"
style 0-55 dim
30| <blank>
31| "System prompt "
31| <blank>
32| "System prompt "
style 0-12 fg=bright-magenta bold
32| "You are an AI agent powered by the DeepSeek Harness SDK."
33| " "
34| "Paths prefixed with @ are files explicitly referenced by"
35| "the user. Use the read tool when their contents are "
36| "needed; do not claim to have inspected a file before "
37| "reading it. "
38| <blank>
39| "Registered tools "
33| "You are an AI agent powered by the DeepSeek Harness SDK."
34| " "
35| "Paths prefixed with @ are files explicitly referenced by"
36| "the user. Use the read tool when their contents are "
37| "needed; do not claim to have inspected a file before "
38| "reading it. "
39| <blank>
40| "Registered tools "
style 0-15 fg=bright-magenta bold
40| "read, write "
41| <blank>
42| "/workspace/project (tui-staging) deepseek-v4-pro ↑1.3k"
41| "read, write "
42| <blank>
43| "/workspace/project (tui-staging) deepseek-v4-pro ↑1.3k"
style 0-17 fg=bright-magenta bold
style 18-31 dim
style 34-48 dim
style 51-55 dim
43| " dsh > "
44| " dsh > "
style 1-3 fg=bright-magenta bold
style 5-6 dim
style 7-7 inverse

View File

@@ -21,68 +21,68 @@ buffer
style 0-2 fg=bright-magenta bold underline
9| "inspect this session "
10| <blank>
11| "╭─ Session status ───────────────────────────────────────────────────────────────╮"
11| "╭─ Session status ────────────────────────────────────────────────────────────────────────╮"
style 0-2 dim
style 3-16 fg=bright-magenta bold
style 17-81 dim
12| "│ Session: main-session │"
style 17-90 dim
12| "│ Session: main-session │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
13| "│ Title: Inspect session diagnostics │"
style 90-90 dim
13| "│ Title: Inspect session diagnostics │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
14| "│ Directory: /workspace/project │"
style 90-90 dim
14| "│ Directory: /workspace/project │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
15| "│ Model: deepseek/deepseek-v4-pro (effort default; reasoning blocks shown) │"
style 90-90 dim
15| "│ Model: deepseek-official/deepseek-v4-pro (effort default; reasoning blocks shown) │"
style 0-0 dim
style 3-12 dim
style 40-79 dim
style 81-81 dim
16| "│ │"
style 49-88 dim
style 90-90 dim
16| "│ │"
style 0-0 dim
style 81-81 dim
17| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 tool call │"
style 90-90 dim
17| "│ Agent: idle · 8 events · 1 turn · 1 step · 1 tool call │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
18| "│ │"
style 90-90 dim
18| "│ │"
style 0-0 dim
style 81-81 dim
19| "│ Tokens: 1,250 input + 340 output │"
style 90-90 dim
19| "│ Tokens: 1,250 input + 340 output │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
20| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read + 250 write) │"
style 90-90 dim
20| "│ KV cache: [███████████░░░░░] 67% hit (3,000 read + 250 write) │"
style 0-0 dim
style 3-12 dim
style 15-15 dim
style 16-26 fg=bright-magenta
style 27-32 dim
style 81-81 dim
21| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / 128,000) │"
style 90-90 dim
21| "│ Context: [█████░░░░░░░░░░░] 33% used (42,000 / 128,000) │"
style 0-0 dim
style 3-12 dim
style 15-15 dim
style 16-20 fg=bright-magenta
style 21-32 dim
style 81-81 dim
22| "│ │"
style 90-90 dim
22| "│ │"
style 0-0 dim
style 81-81 dim
23| "│ Created: 2026-07-22 09:10:11 UTC │"
style 90-90 dim
23| "│ Created: 2026-07-22 09:10:11 UTC │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
24| "│ Active: 2026-07-22 09:10:11 UTC │"
style 90-90 dim
24| "│ Active: 2026-07-22 09:10:11 UTC │"
style 0-0 dim
style 3-12 dim
style 81-81 dim
25| "╰────────────────────────────────────────────────────────────────────────────────╯"
style 0-81 dim
style 90-90 dim
25| "╰─────────────────────────────────────────────────────────────────────────────────────────╯"
style 0-90 dim
26| <blank>
27| "System prompt "
style 0-12 fg=bright-magenta bold

View File

@@ -554,7 +554,7 @@ describe('TUI terminal-state snapshots', () => {
description: 'Audit terminal states from independent angles',
phases: [
{ title: 'Inspect', detail: 'Map renderer branches' },
{ title: 'Verify', detail: 'Challenge missing states', provider: 'deepseek', model: 'deepseek-v4-flash' },
{ title: 'Verify', detail: 'Challenge missing states', provider: 'deepseek-official', model: 'deepseek-v4-flash' },
],
},
args: { packages: ['ui/tui', 'workflow/tool-workflow'] },
@@ -866,13 +866,13 @@ describe('TUI terminal-state snapshots', () => {
{ type: 'turn/start', seq: 0, time: Date.parse(`${day}T00:00:01Z`), data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
{ type: 'user/message', seq: 1, time: Date.parse(`${day}T00:00:02Z`), data: createUserMessage({ content: [{ type: 'text', text: 'restore the selector' }], source: { kind: 'user' } }), surfaceOp: 'append' },
{ type: 'step/start', seq: 2, time: Date.parse(`${day}T00:00:03Z`), data: { turn: 1, step: 1 } },
{ type: 'request/header', seq: 3, time: Date.parse(`${day}T00:00:04Z`), data: { header: { config: { provider: 'deepseek', model: 'deepseek-v4-pro' } }, reason: 'initial' } },
{ type: 'request/header', seq: 3, time: Date.parse(`${day}T00:00:04Z`), data: { header: { config: { provider: 'deepseek-official', model: 'deepseek-v4-pro' } }, reason: 'initial' } },
{ type: 'assistant/message', seq: 4, time: Date.parse(`${day}T00:00:05Z`), data: {
turn: 1, step: 1,
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: 'ready' }],
source: { kind: 'model', provider: 'deepseek', model: 'deepseek-v4-pro' },
source: { kind: 'model', provider: 'deepseek-official', model: 'deepseek-v4-pro' },
}),
}, surfaceOp: 'append' },
{ type: 'step/end', seq: 5, time: Date.parse(`${day}T00:00:06Z`), data: { turn: 1, step: 1 } },
@@ -913,7 +913,7 @@ describe('TUI terminal-state snapshots', () => {
const harness = await setupSnapshot({
contextWindow: 128_000,
contextTokens: 42_000,
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-pro' },
agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-pro' },
tools: {
read: {
name: 'read',

View File

@@ -247,7 +247,7 @@ describe('goodbye message and /resume', () => {
({ version: 0, id: SessionId(id), createdAt, cwd })
const resumeEvents = (
title: string,
provider = 'deepseek',
provider = 'deepseek-official',
time = 100,
reason: TurnEndReason = { kind: 'completed' },
): SessionEvent[] => [
@@ -319,8 +319,8 @@ describe('goodbye message and /resume', () => {
sessionPersistence: {
list: async () => [older, newer, header('foreign-session', 3000, '/elsewhere')],
load: async id => id === newer.id
? { meta: newer, events: resumeEvents('Newer product work', 'deepseek', 300) }
: { meta: older, events: resumeEvents('Older investigation', 'deepseek', 100) },
? { meta: newer, events: resumeEvents('Newer product work', 'deepseek-official', 300) }
: { meta: older, events: resumeEvents('Older investigation', 'deepseek-official', 100) },
},
})
result.terminal.send('/resume')
@@ -419,7 +419,7 @@ describe('goodbye message and /resume', () => {
list: async () => targets,
load: async id => ({
meta: targets.find(target => target.id === id)!,
events: resumeEvents(`Paged ${id.slice('paged-'.length)}`, 'deepseek', 1000 - Number(id.slice('paged-'.length)) * 10),
events: resumeEvents(`Paged ${id.slice('paged-'.length)}`, 'deepseek-official', 1000 - Number(id.slice('paged-'.length)) * 10),
}),
},
})
@@ -475,7 +475,7 @@ describe('goodbye message and /resume', () => {
cwd: '/workspace',
sessionPersistence: {
list: async () => [target],
load: async () => ({ meta: target, events: resumeEvents(`Turn ${label}`, 'deepseek', 100, reason) }),
load: async () => ({ meta: target, events: resumeEvents(`Turn ${label}`, 'deepseek-official', 100, reason) }),
},
})
result.terminal.send('/resume')
@@ -711,7 +711,7 @@ describe('goodbye message and /resume', () => {
it('falls back to assistant provenance and header creation time for sparse logs', async () => {
const assistantOnly = header('assistant-route', 20, '/workspace')
const empty = header('empty-log', 10, '/workspace')
const events = resumeEvents('Assistant route', 'deepseek')
const events = resumeEvents('Assistant route', 'deepseek-official')
.filter(event => event.type !== 'request/header')
.map((event, seq) => ({ ...event, seq })) as SessionEvent[]
const result = await setup({
@@ -726,7 +726,7 @@ describe('goodbye message and /resume', () => {
result.terminal.send('/resume')
result.terminal.send('\r')
await tick(); await tick()
expect(result.terminal.output).toContain('deepseek/model-1')
expect(result.terminal.output).toContain('deepseek-official/model-1')
expect(result.terminal.output).toContain(new Date(empty.createdAt).toISOString())
await dispose(result)
})
@@ -2397,7 +2397,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
contextWindow: 128_000,
contextTokens: 42_000,
config: { showReasoning: false },
agentOptions: { provider: 'deepseek', model: 'deepseek-v4-pro' },
agentOptions: { provider: 'deepseek-official', model: 'deepseek-v4-pro' },
tools: {
read: {
name: 'read', description: 'Read a file', parameters: {},
@@ -2445,7 +2445,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
expect(result.terminal.output).toContain('main-session')
expect(result.terminal.output).toContain('Inspect status \\x1b]2;unsafe\\x07')
expect(result.terminal.output).toContain('/workspace/status')
expect(result.terminal.output).toContain('deepseek/deepseek-v4-pro (effort default; reasoning blocks')
expect(result.terminal.output).toContain('deepseek-official/deepseek-v4-pro (effort default; reasoning blocks')
expect(result.terminal.output).toContain('hidden)')
// 6 domain events + the /status invocation's own command/run (open turn: joined directly).
expect(result.terminal.output).toContain('running · 7 events · 1 turn · 1 step · 2 tool calls')
@@ -3601,7 +3601,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
const failed = await setup({
catalog: {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [],
listModels: () => Promise.reject(new Error('catalog offline')),
resolveModelInfo: () => Promise.reject(new Error('capacity offline')),
@@ -3617,8 +3617,8 @@ describe('pi-tui chat lifecycle and transcript', () => {
const reasoningFailed = await setup({
catalog: {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
models: [{ provider: 'deepseek', id: 'model-1', name: 'Model One' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [{ provider: 'deepseek-official', id: 'model-1', name: 'Model One' }],
resolveModelInfo: () => Promise.reject(new Error('reasoning metadata offline')),
},
})
@@ -3634,7 +3634,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
const deferred = Promise.withResolvers<never[]>()
const result = await setup({
catalog: {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [],
listModels: () => deferred.promise,
},
@@ -3650,7 +3650,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
const rejected = Promise.withResolvers<never[]>()
const rejectedResult = await setup({
catalog: {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [],
listModels: () => rejected.promise,
},
@@ -3667,7 +3667,7 @@ describe('pi-tui chat lifecycle and transcript', () => {
const contextResult = await setup({
contextTokens: 99,
catalog: {
providers: [{ id: 'deepseek', name: 'DeepSeek' }],
providers: [{ id: 'deepseek-official', name: 'DeepSeek' }],
models: [],
resolveModelInfo: () => context.promise.then(value => ({ context: value })),
},
@@ -4395,6 +4395,14 @@ describe('tool cards and surface replay', () => {
name: 'knownXml', description: '', parameters: {}, output: UNUSED_TOOL_OUTPUT, execute: async () => [],
presentCall: () => ({ card: 'generic', title: 'Known XML' }),
},
// A web card carries no `content` copy, so it falls back to the raw result
// content, which must still render through the dim Markdown path (bold
// markers stripped) rather than as bare text.
webCard: {
name: 'webCard', description: '', parameters: {}, output: UNUSED_TOOL_OUTPUT, execute: async () => [],
presentCall: () => ({ card: 'generic', title: 'Fetch page', kind: 'fetch' }),
presentResult: () => ({ card: 'web', kind: 'fetch', title: 'https://a.test', url: 'https://a.test', statusCode: 200, truncated: false }),
},
}
it('uses terminal, diff, generic, fallback, and collapsed tool presentations', async () => {
@@ -4415,6 +4423,7 @@ describe('tool cards and surface replay', () => {
['c11', 'terminalResult', '{}'],
['c12', 'symbolic', '{}'],
['c13', 'knownXml', '{}'],
['c16', 'webCard', '{}'],
] as const
appendAssistant(result.session, [
{ type: 'text', text: 'Calling tools' },
@@ -4508,6 +4517,14 @@ describe('tool cards and surface replay', () => {
isError: false,
}),
}, { surfaceOp: 'append' })
result.session.append('tool/result', {
turn: 1, step: 1,
message: createToolResultMessage({
callId: 'c16' as never,
content: [{ type: 'text', text: 'Fetched **body** text' }],
isError: false,
}),
}, { surfaceOp: 'append' })
result.session.append('tool/result', {
turn: 1,
step: 1,
@@ -4557,6 +4574,11 @@ describe('tool cards and surface replay', () => {
expect(output).toContain('Empty card')
expect(output).toContain('converted terminal')
expect(output).toContain('<known><value>literal</value></known>')
// A web card carries no `content` copy, so it falls back to the raw result
// content, which still renders through the dim Markdown path: the bold
// markers are stripped rather than shown literally.
expect(output).toContain('Fetched body text')
expect(output).not.toContain('Fetched **body** text')
expect(output).toContain('path: /tmp/a.txt')
expect(output).toContain('line (number="1"): hello')
expect(output).not.toContain('<result>')