Files
ycode/scripts/conversation-renderer-regression.tsx
T

754 lines
37 KiB
TypeScript

import assert from 'node:assert/strict'
import { renderToStaticMarkup } from 'react-dom/server'
import MessageBubble from '../src/renderer/src/components/MessageBubble'
import type { ChatMessage } from '../src/shared/types'
import {
AskUserQuestion,
PlanCreateBlock,
PlanUpdateBlock,
PermissionConfirm,
QuestionFloating,
QuestionAnswerTranscript,
TaskBlock,
ThinkingBlock,
ToolCallCard,
ToolCallRenderer,
ToolGroup,
buildQuestionAnswerPayload,
computeToolGroupVisibleStart,
getPermissionChoices,
getCapturedToolKind,
selectPlanTasks
} from '../src/renderer/src/workbuddy/conversation'
import { convertReadFile, createListFileOpenRequest, createReadFileOpenRequest } from '../src/renderer/src/workbuddy/conversation/ToolCallRenderer'
import { SourceToolHeader } from '../src/renderer/src/workbuddy/conversation/SourceToolHeader'
import { resolveSourceToolIcon } from '../src/renderer/src/workbuddy/conversation/SourceToolIcons'
import { SourceToolDiff } from '../src/renderer/src/workbuddy/conversation/SourceToolDiff'
const toolIconMarkup = (name: string) => renderToStaticMarkup(resolveSourceToolIcon(name) ?? null)
assert.match(toolIconMarkup('Read'), /M7\.7407 13\.66/)
assert.match(toolIconMarkup('read_file'), /M7\.7407 13\.66/)
assert.match(toolIconMarkup('PowerShell'), /M0\.1942 3\.7746/)
assert.match(toolIconMarkup('execute_command'), /M0\.1942 3\.7746/)
assert.match(toolIconMarkup('list_dir'), /M0\.1942 3\.7746/)
assert.equal(toolIconMarkup('Inspect'), toolIconMarkup('execute_command'))
assert.match(toolIconMarkup('Grep'), /M1\.2982 9\.8292/)
assert.match(toolIconMarkup('web_search'), /M1\.37 6\.6667/)
assert.match(toolIconMarkup('mcp_call_tool'), /M2\.9742 -0\.1497/)
assert.match(toolIconMarkup('mcp__browser__tabs_list'), /M6\.3745 2\.6414/)
assert.match(toolIconMarkup('mcp__computer-use__open_app'), /M6\.5 11\.0857/)
assert.match(renderToStaticMarkup(resolveSourceToolIcon('mcp__connector-proxy__pick_location', 'captured-icon') ?? null), /class="captured-icon"/)
assert.match(toolIconMarkup('mcp__workbuddy__skill_management'), /M4\.6063 4\.6987/)
const pluginToolAliases = [
'search_plugins', 'suggest_plugin_install', 'list_installed_plugins', 'uninstall_plugin', 'reconnect_plugin',
'mcp__workbuddy__search_plugins', 'mcp__workbuddy__suggest_plugin_install', 'mcp__workbuddy__list_installed_plugins', 'mcp__workbuddy__uninstall_plugin', 'mcp__workbuddy__reconnect_plugin',
'mcp__connector-proxy__search_plugins', 'mcp__connector-proxy__suggest_plugin_install', 'mcp__connector-proxy__list_installed_plugins', 'mcp__connector-proxy__uninstall_plugin', 'mcp__connector-proxy__reconnect_plugin'
]
for (const alias of pluginToolAliases) assert.match(toolIconMarkup(alias), /M5\.7784 2\.3425/)
assert.match(renderToStaticMarkup(resolveSourceToolIcon('mcp__connector-proxy__reconnect_plugin', 'plugin-icon') ?? null), /class="plugin-icon"/)
assert.match(toolIconMarkup('unregistered_tool'), /M2\.9742 -0\.1497/)
assert.match(toolIconMarkup('mcp__unknown__tool'), /M2\.9742 -0\.1497/)
const tool = {
id: 'tool-1',
name: 'execute_command',
status: 'cancelled' as const,
args: { command: 'echo test' },
result: { output: 'first line\nFINAL OUTPUT SENTINEL' }
}
const presentedFiles = renderToStaticMarkup(<ToolCallRenderer tool={{ id: 'present-files', name: 'present_files', status: 'success',
result: { result: '{"files":["result.html"]}' } }} />)
assert.match(presentedFiles, /cr-tool-compact-misc__pre/)
assert.match(presentedFiles, /result.html/)
assert.match(presentedFiles, /aria-expanded="false"/)
assert.doesNotMatch(presentedFiles, /cr-tool-call__data/)
const failedFiles = renderToStaticMarkup(<ToolCallRenderer tool={{ id: 'present-files', name: 'present_files', status: 'error',
result: { result: 'failed' } }} />)
assert.doesNotMatch(failedFiles, /aria-expanded=/)
const toolMarkup = renderToStaticMarkup(<ToolCallCard tool={tool} defaultExpanded />)
assert.match(toolMarkup, /execute_command/)
assert.match(toolMarkup, /Cancelled/)
assert.match(toolMarkup, /echo test/)
assert.match(toolMarkup, /FINAL OUTPUT SENTINEL/)
assert.match(renderToStaticMarkup(<ToolCallCard tool={{ ...tool, status: 'failed' }} />), /Failed/)
const completeOutput = 'Complete output that exceeds the summary. LAST OUTPUT SENTINEL'
const outputPreferred = renderToStaticMarkup(
<ToolCallCard
tool={{ id: 'output-1', name: 'custom', status: 'success', output: completeOutput, result: { output: 'short summary only' } }}
defaultExpanded
/>
)
assert.match(outputPreferred, /Complete output that exceeds the summary/)
assert.match(outputPreferred, /LAST OUTPUT SENTINEL/)
assert.doesNotMatch(outputPreferred, /short summary only/)
const collapsedGroup = renderToStaticMarkup(
<ToolGroup stats={{ toolCallCount: 2, messageCount: 1, errorCount: 1 }} summary={{ text: 'Running command' }}>
<span>group child</span>
</ToolGroup>
)
assert.match(collapsedGroup, /aria-expanded="false"/)
assert.match(collapsedGroup, /Running command/)
assert.match(collapsedGroup, /group child/, 'source collapse retains mounted children for height/opacity transitions')
assert.match(collapsedGroup, /aria-hidden="true" inert=""/)
const expandedGroup = renderToStaticMarkup(
<ToolGroup stats={{ toolCallCount: 2, messageCount: 1 }} defaultExpanded>
<span>group child</span>
</ToolGroup>
)
assert.match(expandedGroup, /Tools/)
assert.match(expandedGroup, /group child/)
assert.equal(computeToolGroupVisibleStart(20, undefined, 1), 5)
assert.equal(computeToolGroupVisibleStart(20, undefined, 2), 0)
const pagedGroup = renderToStaticMarkup(
<ToolGroup stats={{ toolCallCount: 20, messageCount: 0 }} defaultExpanded>
{Array.from({ length: 20 }, (_, index) => <span key={index}>{`item-${index}`}</span>)}
</ToolGroup>
)
assert.match(pagedGroup, /Show 5 earlier/)
assert.doesNotMatch(pagedGroup, /item-0</)
assert.match(pagedGroup, /item-19</)
const sourceSummaryGroup = renderToStaticMarkup(
<ToolGroup variant="summary" stats={{ toolCallCount: 20, messageCount: 20 }} summary={{ text: '运行命令' }} defaultExpanded>
{Array.from({ length: 20 }, (_, index) => <span key={index}>{`summary-item-${index}`}</span>)}
</ToolGroup>
)
assert.match(sourceSummaryGroup, /cr-collapse--summary/)
assert.match(sourceSummaryGroup, /content-inner--summary/)
assert.match(sourceSummaryGroup, /summary-item-0</)
assert.match(sourceSummaryGroup, /summary-item-19</)
assert.doesNotMatch(sourceSummaryGroup, /Show \d+ earlier/)
const permissionOptions = [
{ optionId: 'once', kind: 'allow_once', name: 'Allow once' },
{ optionId: 'always', kind: 'allow_always', name: 'Always allow' },
{ optionId: 'reject', kind: 'reject_once', name: 'Reject' }
]
assert.equal(getPermissionChoices(permissionOptions).primary?.optionId, 'once')
assert.equal(getPermissionChoices(permissionOptions).secondary?.optionId, 'always')
assert.equal(getPermissionChoices(permissionOptions, 'session').primary?.optionId, 'always')
const pendingPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-1"
toolName="execute_command"
toolKind="computerUse"
options={permissionOptions}
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(pendingPermission, /role="dialog"/)
assert.match(pendingPermission, /data-pending-id="permission-1"/)
assert.match(pendingPermission, /data-option-id="once"/)
assert.match(pendingPermission, /Always allow/)
assert.match(pendingPermission, /Reject/)
const answeredPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-2"
toolName="image_gen"
options={permissionOptions}
status="answered"
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(answeredPermission, /Approved/)
assert.doesNotMatch(answeredPermission, /data-option-id=/)
const respondingPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-responding"
toolName="image_gen"
options={permissionOptions}
status="responding"
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(respondingPermission, /Sending decision/)
assert.match(respondingPermission, /disabled=""/)
const failedPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-error"
toolName="image_gen"
options={permissionOptions}
status="error"
error="Broker unavailable"
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(failedPermission, /Broker unavailable/)
assert.match(failedPermission, /data-option-id="once"/)
const rejectedPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-rejected"
toolName="image_gen"
options={permissionOptions}
status="rejected"
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(rejectedPermission, /Rejected/)
const cancelledPermission = renderToStaticMarkup(
<PermissionConfirm
pendingId="permission-3"
toolName="image_gen"
options={permissionOptions}
status="cancelled"
resolve={() => undefined}
reject={() => undefined}
/>
)
assert.match(cancelledPermission, /Cancelled/)
const terminalApprovalMessage: ChatMessage = {
id: 'message-terminal-approval',
role: 'assistant',
content: 'Browser opened.',
parts: [{ type: 'text', text: 'Browser opened.' }],
status: 'done',
createdAt: 1,
interactions: [{
id: 'approval-terminal',
runId: 'run-terminal',
toolCallId: 'browser-tool',
title: 'Allow browser access?',
status: 'answered',
kind: 'approval',
requestId: 'request-terminal',
options: permissionOptions
}]
}
const terminalApprovalMarkup = renderToStaticMarkup(<MessageBubble message={terminalApprovalMessage} />)
assert.doesNotMatch(terminalApprovalMarkup, /conversation-high-credit-approval/,
'a completed non-inline approval must not leave a terminal card in the conversation')
const activeApprovalMarkup = renderToStaticMarkup(<MessageBubble message={{
...terminalApprovalMessage,
status: 'streaming',
interactions: terminalApprovalMessage.interactions?.map(interaction => ({ ...interaction, status: 'pending' as const }))
}} />)
assert.doesNotMatch(activeApprovalMarkup, /conversation-high-credit-approval/,
'an active approval belongs to the composer floating slot, not the conversation stream')
const questionConfig = {
title: 'Choose a runtime',
questions: [
{
id: 'runtime',
question: 'Which runtime?',
options: [
{ id: 'node', question: 'Node.js', description: 'JavaScript runtime' },
{ id: 'deno', question: 'Deno' }
]
},
{
id: 'features',
question: 'Select features',
multiSelect: true,
options: ['Tests', 'Lint']
}
]
}
const activeQuestionMessage: ChatMessage = {
id: 'message-active-question',
role: 'assistant',
content: '',
tools: [{
id: 'question-tool',
name: 'AskUserQuestion',
args: { questions: questionConfig.questions },
argsRaw: '{}',
status: 'running'
}],
parts: [{ type: 'tool', toolId: 'question-tool' }],
status: 'streaming',
createdAt: 2,
interactions: [{
id: 'question-active',
runId: 'run-question',
toolCallId: 'question-tool',
title: 'Need input',
status: 'pending',
kind: 'question',
questions: questionConfig.questions.map(question => ({ id: question.id, question: question.question, options: question.options }))
}]
}
const activeQuestionMarkup = renderToStaticMarkup(<MessageBubble message={activeQuestionMessage} />)
assert.doesNotMatch(activeQuestionMarkup, /ask-user-question/,
'an active user question must render only in the composer floating slot')
const questionMarkup = renderToStaticMarkup(<QuestionFloating config={questionConfig} />)
assert.match(questionMarkup, /Which runtime\?/)
assert.match(questionMarkup, /Node\.js/)
assert.match(questionMarkup, /JavaScript runtime/)
assert.match(questionMarkup, /1\/2/)
assert.match(questionMarkup, /Add your own answer/)
assert.match(questionMarkup, /role="button" aria-pressed="true"/)
const answer = buildQuestionAnswerPayload(
questionConfig.questions,
{ runtime: ['node'], features: ['Tests'] },
{ runtime: '', features: 'Custom feature' },
{ features: true },
questionConfig.title
)
assert.deepEqual(answer, {
title: 'Choose a runtime',
questions: [
{ id: 'runtime', question: 'Which runtime?', answers: ['node'] },
{ id: 'features', question: 'Select features', answers: ['Tests', 'Custom feature'] }
]
})
const pendingQuestion = renderToStaticMarkup(
<AskUserQuestion tool={{ name: 'AskUserQuestion', status: 'pending' }} />
)
assert.match(pendingQuestion, /Waiting for your answer/)
const completedQuestion = renderToStaticMarkup(
<AskUserQuestion
tool={{
name: 'AskUserQuestion',
status: 'executed',
metaData: { questionAnswer: { questions: [{ id: 'q1', question: 'Where?', answers: ['Here'] }] } }
}}
contentSettled={false}
/>
)
assert.match(completedQuestion, /Question for you/)
assert.match(completedQuestion, /Where\?/)
assert.match(completedQuestion, /Here/)
const cancelledQuestion = renderToStaticMarkup(
<AskUserQuestion tool={{ name: 'AskUserQuestion', status: 'cancelled' }} />
)
assert.match(cancelledQuestion, /Question cancelled/)
const failedQuestion = renderToStaticMarkup(
<AskUserQuestion tool={{ name: 'AskUserQuestion', status: 'failed' }} />
)
assert.match(failedQuestion, /Question failed/)
const skippedQuestion = renderToStaticMarkup(
<AskUserQuestion tool={{ name: 'AskUserQuestion', status: 'executed', metadata: { questionSkipped: true } }} />
)
assert.match(skippedQuestion, /Question skipped/)
const transcript = renderToStaticMarkup(
<QuestionAnswerTranscript questionAnswer={{ questions: [{ id: 'q2', question: 'Skipped?', answers: [] }] }} />
)
assert.match(transcript, /\[Skipped\]/)
const collapsedThinking = renderToStaticMarkup(<ThinkingBlock text="Hidden reasoning" isFinished />)
// The source collapse keeps its content mounted for CSS transitions; it must be inaccessible while closed.
assert.match(collapsedThinking, /cr-collapse--collapsed/)
assert.match(collapsedThinking, /class="cr-collapse__content" aria-hidden="true" inert=""/)
const expandedThinking = renderToStaticMarkup(<ThinkingBlock text="Visible reasoning" isFinished isLastContent renderMarkdown={text => <strong>{text}</strong>} />)
assert.match(expandedThinking, /Visible reasoning/)
assert.match(expandedThinking, /<strong>Visible reasoning<\/strong>/)
assert.doesNotMatch(expandedThinking, /cr-collapse--collapsed/)
const planTasks = Array.from({ length: 8 }, (_, index) => ({
id: `task-${index}`,
content: `Plan task ${index}`,
status: index < 5 ? 'completed' : index === 5 ? 'in_progress' : 'pending'
}))
assert.deepEqual(selectPlanTasks(planTasks, 3), {
visible: planTasks.slice(4, 7), hiddenCount: 5, currentIndex: 5, startIndex: 4
})
const successfulPlan = renderToStaticMarkup(
<PlanCreateBlock data={{ id: 'plan-1', name: 'Release plan', overview: 'Ship the release', todolist: planTasks }} status="success" maxDisplayTasks={3} onViewPlan={() => undefined} onDownloadPlan={() => undefined} />
)
assert.match(successfulPlan, /Release plan/)
assert.match(successfulPlan, /Plan task 5/)
assert.doesNotMatch(successfulPlan, /Current/, 'the captured plan list has no added Current label')
assert.match(successfulPlan, /\+ 5 更多/)
assert.match(successfulPlan, /Download plan/)
assert.doesNotMatch(successfulPlan, /Plan task 0/)
const failedPlan = renderToStaticMarkup(
<PlanCreateBlock data={{ id: 'plan-2', name: 'Broken plan' }} status="error" />
)
assert.match(failedPlan, /Failed/)
const activePlanUpdate = renderToStaticMarkup(
<PlanUpdateBlock data={{ name: 'Release plan', status: 'building' }} status="running" />
)
assert.match(activePlanUpdate, /Release plan confirmed/)
assert.match(activePlanUpdate, /cb-plan-update-spinner/)
const cancelledTask = renderToStaticMarkup(
<TaskBlock data={{ description: 'Run checks', subagentName: 'builder', finalResult: 'Stopped output', toolInfo: [{ name: 'execute_command', needApprove: true, toolCallId: 'inner-tool' }] }} status="cancelled" />
)
assert.match(cancelledTask, /任务 builder 子代理: Run checks/)
assert.match(cancelledTask, /任务执行已取消!/)
assert.match(cancelledTask, /data-status="cancelled"/)
assert.equal(getCapturedToolKind('Bash'), 'command')
assert.equal(getCapturedToolKind('bash'), 'command')
assert.equal(getCapturedToolKind('glob'), 'file-list')
assert.equal(getCapturedToolKind('grep'), 'file-list')
assert.equal(getCapturedToolKind('read'), 'read-file')
assert.equal(getCapturedToolKind('NotebookEdit'), 'write-file')
assert.equal(getCapturedToolKind('Grep'), 'file-list')
assert.equal(getCapturedToolKind('WebSearch'), 'web-search')
assert.equal(getCapturedToolKind('todo_write'), 'todo-write')
assert.equal(getCapturedToolKind('TodoWrite'), 'todo-write')
assert.equal(getCapturedToolKind('mcp__ardot__create_design'), 'mcp-display')
assert.equal(getCapturedToolKind('mcp__browser__tab_goto'), 'mcp-display')
assert.equal(getCapturedToolKind('mcp__custom__unknown'), undefined)
assert.equal(getCapturedToolKind('custom_tool'), undefined)
const commandRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'cmd-1', name: 'Bash', status: 'success', args: { command: 'npm test' }, result: { result: { stdout: 'all checks passed', exitCode: 0 } } }} defaultExpanded />
)
assert.match(commandRenderer, /npm test/)
assert.match(commandRenderer, /all checks passed/)
assert.match(commandRenderer, /cr-tool-exec__output/)
assert.match(commandRenderer, /cr-tool-head__primary/)
assert.doesNotMatch(commandRenderer, /Exit code 0/)
assert.doesNotMatch(commandRenderer, /Full output/)
assert.doesNotMatch(commandRenderer, /Arguments/)
assert.equal(getCapturedToolKind('powershell'), 'command')
const runningCommandRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'cmd-running', name: 'bash', status: 'running', args: {
command: 'ls src', description: 'List contents of src directory'
}, output: 'first output snapshot\nsecond output snapshot' }} defaultExpanded />
)
assert.match(runningCommandRenderer, /List contents of src directory/)
assert.match(runningCommandRenderer, /aria-expanded="false"/,
'a running command stays on its one-line summary even when the parent requests expanded details')
assert.match(runningCommandRenderer, /cr-tool-exp__content-shell" aria-hidden="true" inert=""/)
assert.equal((runningCommandRenderer.match(/class="cr-tool-exec__output"/g) ?? []).length, 1,
'the command output remains one replaceable details region')
assert.match(runningCommandRenderer, /first output snapshot\nsecond output snapshot/)
const runningGlobRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'glob-running', name: 'glob', status: 'running', intent: 'Inspecting source layout', args: { path: 'src' }, output: 'src/index.ts\nsrc/main.ts' }} />
)
assert.match(runningGlobRenderer, /cr-tool-list-files__header/)
assert.match(runningGlobRenderer, /搜索中/)
assert.match(runningGlobRenderer, /title="src">Inspecting source layout<\/span>/)
assert.match(runningGlobRenderer, /cr-tool-head__secondary">src<\/span>/)
assert.match(runningGlobRenderer, /aria-expanded="false"/)
assert.match(runningGlobRenderer, /src\/index\.ts\nsrc\/main\.ts/)
const inferredRunningListRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'list-no-intent', name: 'list_dir', status: 'running', args: { path: 'src' }, output: 'index.ts\nmain.ts' }} />
)
assert.match(inferredRunningListRenderer, /List contents of src directory/,
'a list_dir call without provider intent still gets a descriptive one-line action title')
assert.doesNotMatch(inferredRunningListRenderer, /cr-tool-head__secondary/,
'the inferred action title does not repeat the directory as secondary text')
assert.match(inferredRunningListRenderer, /aria-expanded="false"/,
'a running directory listing remains one collapsed, replaceable row')
const inferredRootListRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'list-root-no-path', name: 'list_dir', status: 'success', args: {}, result: { files: [] } }} />
)
assert.match(inferredRootListRenderer, /List contents of the project directory/,
'an omitted list_dir path resolves to the documented workspace root')
const runningCommandWithIntent = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'cmd-intent', name: 'bash', status: 'running', intent: 'List contents of src directory', args: { command: 'ls src' }, output: 'file-a.ts\nfile-b.ts' }} />
)
assert.match(runningCommandWithIntent, /List contents of src directory/)
assert.match(runningCommandWithIntent, /aria-expanded="false"/)
const emptySuccessfulCommand = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'cmd-empty', name: 'Bash', status: 'success', args: { command: 'true' }, result: { stdout: '(empty)', stderr: '(empty)', exitCode: 0, message: 'Operation completed' } }} defaultExpanded />
)
assert.doesNotMatch(emptySuccessfulCommand, /cr-tool-exec__output/)
assert.doesNotMatch(emptySuccessfulCommand, /Command failed/)
assert.match(emptySuccessfulCommand, /cr-tool-exec__ok/)
assert.match(emptySuccessfulCommand, /运行成功/)
const commandOutputFallback = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'cmd-output', name: 'Bash', status: 'success', args: { command: 'run task' }, output: completeOutput, result: { success: true, exitCode: 0 } }} defaultExpanded />
)
assert.match(commandOutputFallback, /LAST OUTPUT SENTINEL/)
assert.doesNotMatch(commandOutputFallback, /"success": true/)
assert.doesNotMatch(commandOutputFallback, /Full output/)
const failedCommandRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'cmd-failed', name: 'Bash', status: 'failed', args: { command: 'run failing task' },
result: { result: { stdout: 'started', stderr: 'operation rejected', exitCode: 7 } }
}} />
)
assert.match(failedCommandRenderer, /started/)
assert.match(failedCommandRenderer, /operation rejected/)
assert.match(failedCommandRenderer, /执行失败 \(退出码: 7\)/)
assert.doesNotMatch(failedCommandRenderer, /Exit code 7/)
const readRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-1', name: 'Read', status: 'executed', args: { file_path: 'src/main.ts' }, result: { content: 'line three\nline four' } }} defaultExpanded />
)
assert.match(readRenderer, /src\/main\.ts/)
assert.match(readRenderer, /class="cr-tool-head__status">已读取<\/span>/)
assert.match(readRenderer, /cr-tool-head__icon[^>]*><svg/)
assert.doesNotMatch(readRenderer, /line three/, 'source read card is header only; content remains in the file-open adapter')
const readIntentRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-intent', name: 'read_file', status: 'running', intent: 'Reading project handover status', args: { path: 'docs/handover.md' } }} />
)
assert.match(readIntentRenderer, /Reading project handover status/)
assert.match(readIntentRenderer, /handover\.md/)
const inferredReadRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-no-intent', name: 'read_file', status: 'success', args: { path: 'docs/README.md', offset: 10, limit: 5 }, result: { content: 'contents' } }} />
)
assert.match(inferredReadRenderer, /Read file docs\/README\.md \(lines 10-14\)/,
'a read_file call without provider intent still gets a descriptive action title')
const readOutputFallback = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-output', name: 'Read', status: 'executed', args: { file_path: 'src/result.ts' }, result: { success: true }, output: 'complete file output sentinel' }} defaultExpanded />
)
assert.doesNotMatch(readOutputFallback, /complete file output sentinel/)
const openedReadTool = {
id: 'read-open', name: 'Read', status: 'executed' as const,
args: { file_path: 'src/open.ts' },
result: [{ type: 'text', text: 'line four\nline five' }]
}
assert.deepEqual(createReadFileOpenRequest(openedReadTool), {
toolCallId: 'read-open', toolName: 'Read', filePath: 'src/open.ts', startLine: undefined, endLine: undefined, content: 'line four\nline five'
})
const numberedReadTool = {
id: 'read-numbered-open', name: 'Read', status: 'executed' as const,
args: { file_path: 'src/numbered.ts' }, result: [{ type: 'text', text: '1→line one\n2→line two\n' }]
}
assert.equal(createReadFileOpenRequest(numberedReadTool).content, 'line one\nline two')
const partialReadTool = {
id: 'read-partial-open', name: 'Read', status: 'executed' as const,
args: { file_path: 'src/partial.ts', startLine: 10, endLine: 12 }, result: [{ type: 'text', text: 'partial chunk' }]
}
assert.equal(convertReadFile(partialReadTool).content, undefined)
assert.deepEqual(createReadFileOpenRequest(partialReadTool), {
toolCallId: 'read-partial-open', toolName: 'Read', filePath: 'src/partial.ts', startLine: 10, endLine: 12, content: undefined
})
const missingReadPathRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-missing-path', name: 'Read', status: 'executed', args: {}, result: [{ type: 'text', text: 'read output without a path' }] }} />
)
assert.match(missingReadPathRenderer, /已读取/)
assert.doesNotMatch(missingReadPathRenderer, /read output without a path/)
const failedReadRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-failed', name: 'Read', status: 'failed', args: { file_path: 'missing.ts' }, result: { result: { error: 'ENOENT: missing.ts' } } }} />
)
assert.match(failedReadRenderer, /cr-tool-head__icon--error/)
assert.doesNotMatch(failedReadRenderer, /ENOENT: missing\.ts/)
const numberedReadRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-2', name: 'read_file', status: 'executed', args: {}, result: [{ type: 'text', text: 'line ' }, { type: 'text', text: 'joined' }] }} defaultExpanded />
)
assert.doesNotMatch(numberedReadRenderer, /line joined/)
assert.doesNotMatch(numberedReadRenderer, /line \njoined/)
const partialReadRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'read-3', name: 'Read', status: 'executed', args: { offset: 10 }, result: [{ type: 'text', text: 'partial chunk' }] }} defaultExpanded />
)
assert.doesNotMatch(partialReadRenderer, /partial chunk/)
const todoWriteRenderer = renderToStaticMarkup(
<ToolCallRenderer
tool={{
id: 'todo-write-1', name: 'todo_write', status: 'executed', isLastContent: true,
args: { _rawInput: { merge: true, totalCount: 3, todos: [
{ id: 'done', content: 'Completed task', status: 'completed' },
{ id: 'active', content: 'Original active task', activeForm: 'Writing the active task', status: 'in_progress' },
{ id: 'pending', content: 'Pending task', status: 'pending' }
] } }
}}
/>
)
assert.match(todoWriteRenderer, /进度更新 1\/3/)
assert.match(todoWriteRenderer, /Completed task/)
assert.match(todoWriteRenderer, /Writing the active task/)
assert.match(todoWriteRenderer, /Pending task/)
assert.match(todoWriteRenderer, /cb-plan-task--completed/)
assert.match(todoWriteRenderer, /cb-plan-task--in_progress/)
assert.match(todoWriteRenderer, /cb-plan-task--pending/)
const todoStepsRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'todo-write-steps', name: 'TodoWrite', status: 'running', args: { steps: [{ title: 'Compatibility step', status: 'doing' }] } }} defaultExpanded />
)
assert.match(todoStepsRenderer, /Compatibility step/)
assert.match(todoStepsRenderer, /cb-plan-task--in_progress/)
const fileListRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'files-1', name: 'Glob', status: 'success', args: { pattern: 'src/**/*.ts' }, result: { result: { results: [{ path: 'src/main.ts' }, { path: 'src/util.ts' }] } } }} defaultExpanded />
)
assert.match(fileListRenderer, /src\/main\.ts/)
assert.match(fileListRenderer, /src\/util\.ts/)
assert.match(fileListRenderer, /搜索文件/)
assert.match(fileListRenderer, /M1\.2982 9\.8292/)
const listFileRequest = createListFileOpenRequest(
{ id: 'files-open', name: 'Grep', status: 'success' },
{ filePath: 'src/main.ts', fileName: 'main.ts', isDirectory: false, startLine: 12, endLine: 13, content: 'matched text' }
)
assert.deepEqual(listFileRequest, { toolCallId: 'files-open', toolName: 'Grep', filePath: 'src/main.ts', startLine: 12, endLine: 13 })
const markdownWebSearchRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'web-1', name: 'WebSearch', status: 'success', args: {},
result: '# Search Results for "captured query"\n\n## 1. [First result](https://example.com/one)\n\n## 2. [Second result](https://example.com/two)'
}} defaultExpanded />
)
assert.match(markdownWebSearchRenderer, /captured query/)
assert.match(markdownWebSearchRenderer, /First result/)
assert.match(markdownWebSearchRenderer, /Second result/)
const structuredWebSearchRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'web-2', name: 'web_search', status: 'success', args: { query: 'fallback query' }, result: { results: [{ title: 'Structured result', url: 'https://example.org' }] },
metaData: { _meta: { 'codebuddy.ai': { rawResponse: { type: 'web_search_tool_result', query: 'structured query', results: [{ title: 'Raw response result', url: 'https://example.net' }] } } } }
}} defaultExpanded />
)
assert.match(structuredWebSearchRenderer, /structured query/)
assert.match(structuredWebSearchRenderer, /Raw response result/)
assert.match(structuredWebSearchRenderer, /网页搜索/)
assert.match(structuredWebSearchRenderer, /M1\.37 6\.6667/)
assert.doesNotMatch(structuredWebSearchRenderer, /Structured result/)
const webFetchRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'web-fetch-icon', name: 'web_fetch', status: 'success', args: { url: 'https://example.org/page' },
result: { result: { title: 'Example page', content: 'Fetched body' } }
}} />
)
assert.match(webFetchRenderer, /网页获取/)
assert.match(webFetchRenderer, /M1\.37 6\.6667/)
const mcpCallRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'mcp-call-1', name: 'mcp_call_tool', status: 'success',
args: { serverName: 'browser', toolName: 'navigate', arguments: { url: 'https://example.com' } },
result: { result: { data: [{ type: 'text', text: 'navigation completed' }] } }
}} />
)
assert.match(mcpCallRenderer, /browser \(navigate\)/)
assert.match(mcpCallRenderer, /cr-tool-mcp__body/)
assert.match(mcpCallRenderer, /https:\/\/example\.com/)
assert.match(mcpCallRenderer, /navigation completed/)
assert.match(mcpCallRenderer, /运行成功/)
assert.match(mcpCallRenderer, /M2\.9742 -0\.1497/)
const mcpErrorRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'mcp-call-error', name: 'mcp_call_tool', status: 'failed', result: { result: { error: 'server unavailable', isError: true } } }} />
)
assert.match(mcpErrorRenderer, /server unavailable/)
assert.match(mcpErrorRenderer, /cr-tool-head__icon--error/)
const mcpDisplayRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'mcp-display-1', name: 'mcp__ardot__create_design', status: 'success', args: { name: 'Landing page' },
result: { result: { data: [{ type: 'text', text: 'design created' }] } }
}} />
)
assert.match(mcpDisplayRenderer, /cr-tool-display-list/)
assert.match(mcpDisplayRenderer, /create design/)
assert.match(mcpDisplayRenderer, /Landing page/)
assert.match(mcpDisplayRenderer, /design created/)
assert.match(mcpDisplayRenderer, /M2\.9742 -0\.1497/)
const editRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'edit-1', name: 'Edit', status: 'success', args: { filePath: 'src/main.ts', edits: [{ old_str: 'before', new_str: 'after' }] } }} defaultExpanded onOpenFile={() => undefined} />
)
assert.match(editRenderer, /before/)
assert.match(editRenderer, /after/)
assert.match(editRenderer, /class="cr-tool-head__status">编辑<\/span>/)
assert.match(editRenderer, /1 处修改/)
assert.match(editRenderer, /role="link"/)
const replacementRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'replace-1', name: 'replace_in_file', status: 'success', args: { filePath: 'src/main.ts', old_str: 'before', new_str: 'after' } }} defaultExpanded />
)
assert.match(replacementRenderer, /\+1/)
assert.match(replacementRenderer, /-1/)
const streamedWriteContent = Array.from({ length: 502 }, (_, index) => `line-${index}`).join('\n')
const streamedWriteRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'write-stream-1', name: 'replace_in_file', status: 'running', isLastContent: true, args: { filePath: 'src/new.ts', content: streamedWriteContent } }} />
)
assert.match(streamedWriteRenderer, /修改中/)
assert.match(streamedWriteRenderer, /line-499/)
assert.match(streamedWriteRenderer, /… 省略 2 行/)
assert.doesNotMatch(streamedWriteRenderer, /line-501/)
assert.doesNotMatch(streamedWriteRenderer, /cr-tool-write__content--add/)
const creatingRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'write-create-1', name: 'write_file', status: 'running', args: { filePath: 'src/created.ts', content: 'new content' } }} />
)
assert.match(creatingRenderer, /生成中/)
assert.doesNotMatch(creatingRenderer, /cr-tool-write__content--add/)
const createdRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'write-create-done', name: 'write_file', status: 'success', args: { filePath: 'src/created.ts', content: 'new content' } }} defaultExpanded />
)
assert.match(createdRenderer, /cr-tool-write__content--add/)
const successfulWriteWithOutput = renderToStaticMarkup(
<ToolCallRenderer tool={{
id: 'write-success-output', name: 'write_file', status: 'success',
args: { filePath: 'src/success.ts', content: 'saved content' },
result: { result: { newContent: 'saved content' } }, output: '{"success":true}'
}} defaultExpanded />
)
assert.doesNotMatch(successfulWriteWithOutput, /role="alert"/)
assert.doesNotMatch(successfulWriteWithOutput, /\{"success":true\}/)
const failedWriteWithOutput = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'write-failure-output', name: 'write_file', status: 'failed', output: 'write failed' }} />
)
assert.doesNotMatch(failedWriteWithOutput, /role="alert"/)
assert.doesNotMatch(failedWriteWithOutput, /write failed/)
assert.match(failedWriteWithOutput, /cr-tool-head__icon--error/)
const virtualWriteDiff = renderToStaticMarkup(
<SourceToolDiff oldCode="" newCode={Array.from({ length: 100 }, (_, index) => `line-${index}`).join('\n')} fileName="src/new.ts" />
)
assert.match(virtualWriteDiff, /cr-tool-diff--virtual/)
assert.ok((virtualWriteDiff.match(/cr-tool-diff__virtual-line/g) ?? []).length < 100)
const planRenderer = renderToStaticMarkup(
<ToolCallRenderer
tool={{
id: 'plan-3',
name: 'plan_create',
status: 'success',
result: {
result: {
data: JSON.stringify({
id: 'plan-3',
name: 'Created by tool',
status: 'prepare',
overview: 'Plan overview',
todolist: [{ id: 1, content: 'First task', status: 'completed' }]
})
}
}
}}
/>
)
assert.match(planRenderer, /Created by tool/)
assert.match(planRenderer, /First task/)
assert.doesNotMatch(planRenderer, /查看计划/)
const actionablePlan = renderToStaticMarkup(
<ToolCallRenderer
tool={{ id: 'plan-actions', name: 'plan_create', status: 'success', result: { result: { data: { id: 'plan-actions', name: 'Action plan', status: 'building', todolist: [{ id: 'todo-1', content: 'Clickable task', status: 'in_progress' }] } } } }}
onPlanTaskClick={() => undefined}
onPlanView={() => undefined}
onPlanBuild={() => undefined}
/>
)
assert.match(actionablePlan, /role="button"/)
assert.match(actionablePlan, /查看计划/)
assert.match(actionablePlan, />执行</)
const exitPlanRenderer = renderToStaticMarkup(
<ToolCallRenderer
tool={{
id: 'exit-plan-1',
name: 'exit_plan_mode',
status: 'pending',
isFinished: false,
metaData: { acp_request_permission: { planInfo: { plan: '# Release plan\nShip safely', planFilePath: 'plan.md' } } }
}}
onOpenFile={() => undefined}
/>
)
assert.match(exitPlanRenderer, /role="button"/)
assert.match(exitPlanRenderer, /Release plan/)
assert.match(exitPlanRenderer, /Ship safely/)
const fallbackRenderer = renderToStaticMarkup(
<ToolCallRenderer tool={{ id: 'custom-1', name: 'unregistered_tool', status: 'failed', args: { value: 'kept' } }} defaultExpanded />
)
assert.match(fallbackRenderer, /unregistered_tool/)
assert.match(fallbackRenderer, /kept/)
const sharedToolHeader = renderToStaticMarkup(
<SourceToolHeader primaryContent="Shared header" statusText="Running" isExecuting collapsible expanded onToggle={() => undefined} />
)
assert.match(sharedToolHeader, /class="cr-tool-head cr-tool-head--collapsible cr-tool-head--expanded"/)
assert.match(sharedToolHeader, /cr-tool-head__status--loading/)
assert.match(sharedToolHeader, /aria-expanded="true"/)
console.log('Conversation specialized renderer SSR regression checks passed')