diff --git a/packages/client/ui-primitives/src/markdown/plain-text.ts b/packages/client/ui-primitives/src/markdown/plain-text.ts index 9546e348f0..52d98cbb61 100644 --- a/packages/client/ui-primitives/src/markdown/plain-text.ts +++ b/packages/client/ui-primitives/src/markdown/plain-text.ts @@ -1,6 +1,6 @@ /** * Markdown-to-plain-text projection for compact summaries and labels. - * Parsing shares the renderer's GFM grammar; raw HTML remains literal, links + * Parsing shares the renderer's GFM grammar; raw HTML is omitted, links * keep their labels, images keep alt text, and code keeps its source text. */ @@ -36,7 +36,7 @@ function inlineText(node: MarkdownNode): string { case 'break': return '\n' case 'html': - return node.value ?? '' + return '' case 'thematicBreak': case 'definition': return '' @@ -70,7 +70,7 @@ function blockText(node: MarkdownNode): string { case 'tableCell': return compactInline(inlineText(node)) case 'html': - return node.value?.trim() ?? '' + return '' case 'thematicBreak': case 'definition': return '' diff --git a/packages/client/ui-trajectory/tests/layout.spec.tsx b/packages/client/ui-trajectory/tests/layout.spec.tsx index 8212d8733e..40440a3808 100644 --- a/packages/client/ui-trajectory/tests/layout.spec.tsx +++ b/packages/client/ui-trajectory/tests/layout.spec.tsx @@ -139,7 +139,7 @@ describe('deriveTrajectoryLayout', () => { }, ] as unknown as ConversationSnapshot['nodes'] const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] }) - expect(turns[0]?.groups[0]?.description).toBe('3s bash×2') + expect(turns[0]?.groups[0]?.description).toBe('3 s bash×2') }) it('assigns each user message to its enclosing turn instead of pooling into Turn 1', () => { @@ -172,7 +172,7 @@ describe('deriveTrajectoryLayout', () => { const turns = deriveTrajectoryLayout({ codeDispatches: new Map(), nodes, partial: null, runningCalls: [] }) const message = turns[0]?.groups.flatMap(g => g.cells).find(c => c.kind === 'message') expect(message).toMatchObject({ - text: '仅推理输出', input: 11, output: 22, think: 3, + text: '…', input: 11, output: 22, think: 3, }) }) @@ -236,7 +236,7 @@ describe('run_code sub-dispatch cells', () => { const turns = deriveTrajectoryLayout({ codeDispatches, nodes: runCodeNodes, partial: null, runningCalls: [] }) const cells = turns[0]!.groups.flatMap(g => g.cells) expect(cells.map(c => c.kind)).toEqual(['message', 'tool', 'subtool', 'subtool']) - expect(cells[0]?.text).toBe('请求调用 run_code') + expect(cells[0]?.text).toBe('Tool call only') // Sequential indexes across the interleave; durations from the pair times. expect(cells.map(c => c.index)).toEqual([1, 2, 3, 4]) expect(cells[2]).toMatchObject({ text: 'bash · {"x":1}', timeSeconds: 1 }) diff --git a/packages/client/ui-trajectory/tests/table.spec.tsx b/packages/client/ui-trajectory/tests/table.spec.tsx index c5390f485d..e4a2a1de65 100644 --- a/packages/client/ui-trajectory/tests/table.spec.tsx +++ b/packages/client/ui-trajectory/tests/table.spec.tsx @@ -62,9 +62,9 @@ const FOLD_PROPS = { describe('TrajectoryTable', () => { it('shows assistant timing facts after keyboard selection', () => { - render() - fireEvent.keyDown(screen.getByRole('row', { name: /记录 1,ASSISTANT/ }), { key: 'Enter' }) - fireEvent.click(screen.getByRole('tab', { name: '计时' })) + render() + fireEvent.keyDown(screen.getByRole('row', { name: /ASSISTANT/ }), { key: 'Enter' }) + fireEvent.click(screen.getByRole('button', { name: 'Timing' })) expect(screen.getByText('500 ms')).toBeTruthy() expect(screen.getByText('1.00 s')).toBeTruthy() @@ -84,24 +84,28 @@ describe('TrajectoryTable', () => { }) it('keeps running and failure semantics distinct from record roles', () => { - const view = render() + const view = render() expect(view.container.querySelector('tr[data-kind="tool"][data-running="true"]')).toBeTruthy() expect(view.container.querySelector('tr[data-kind="tool"][data-error="true"]')).toBeTruthy() - fireEvent.click(screen.getByRole('row', { name: /记录 2,TOOL/ })) - expect(screen.getByText('进行中')).toBeTruthy() - fireEvent.click(screen.getByRole('row', { name: /记录 3,TOOL/ })) - expect(screen.getByText('失败')).toBeTruthy() - fireEvent.click(screen.getByRole('tab', { name: '输出' })) + fireEvent.click(screen.getByRole('row', { name: /TOOL, bash \{"command":"pwd"\}/ })) + expect(screen.getByText('Pending')).toBeTruthy() + fireEvent.click(screen.getByRole('row', { name: /TOOL, bash \{"command":"false"\}/ })) + expect(screen.getByText('Failed')).toBeTruthy() + fireEvent.click(screen.getByRole('tab', { name: 'Result' })) expect(screen.getByText('ToolError: non_zero_exit')).toBeTruthy() }) - it('retains the ledger header and record count when collapsed', () => { - render() - expect(screen.getByRole('columnheader', { name: '事件' })).toBeTruthy() - expect(screen.queryByRole('columnheader', { name: 'Tokens' })).toBeNull() - expect(screen.queryByRole('columnheader', { name: '耗时' })).toBeNull() - expect(screen.getByText('3 条记录已收起')).toBeTruthy() - expect(screen.queryByRole('row', { name: /记录 1,ASSISTANT/ })).toBeNull() + it('keeps the first row and a compact summary when a turn is collapsed', () => { + render( + , + ) + expect(screen.queryByRole('columnheader')).toBeNull() + expect(screen.getByRole('row', { name: /ASSISTANT/ })).toBeTruthy() + expect(screen.getByRole('row', { name: /Collapsed turn summary/ })).toBeTruthy() }) }) diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index 70e5a1314d..be64f5c79f 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -178,16 +178,12 @@ describe('tab switching in ConversationRoot', () => { fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' })) expect(screen.queryByText(/turns ·/)).toBeNull() expect(view.container.querySelectorAll('tr[data-turn-start="true"]')).toHaveLength(2) - expect(screen.getAllByLabelText('Step 1')).toHaveLength(2) - expect(screen.getByRole('columnheader', { name: '事件' })).toBeTruthy() - expect(screen.getByRole('columnheader', { name: '内容' })).toBeTruthy() - expect(screen.queryByRole('columnheader', { name: 'Tokens' })).toBeNull() - expect(screen.queryByRole('columnheader', { name: '耗时' })).toBeNull() - expect(screen.getByRole('toolbar', { name: '轨迹工具栏' }).textContent).toContain('4 条记录') - fireEvent.click(screen.getByRole('button', { name: '收起记录' })) - expect(screen.getByText('4 条记录已收起')).toBeTruthy() - fireEvent.click(screen.getByRole('button', { name: '展开记录' })) - expect(screen.getByRole('row', { name: /记录 1,USER/ })).toBeTruthy() + expect(screen.queryByRole('columnheader')).toBeNull() + expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy() + fireEvent.click(screen.getByRole('button', { name: 'Collapse turns' })) + expect(view.container.querySelector('[data-collapsed-summary="turn"]')).toBeTruthy() + fireEvent.click(screen.getByRole('button', { name: 'Expand turns' })) + expect(screen.getByRole('row', { name: /USER/ })).toBeTruthy() expect(screen.queryByTestId('chat-body')).toBeNull() }) @@ -196,19 +192,14 @@ describe('tab switching in ConversationRoot', () => { mount(b.slots) fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' })) - fireEvent.keyDown(screen.getByRole('row', { name: /记录 3,TOOL/ }), { key: 'Enter' }) - expect(screen.getByRole('complementary', { name: '记录详情' })).toBeTruthy() - expect(screen.getByText('记录 #3')).toBeTruthy() + fireEvent.keyDown(screen.getByRole('row', { name: /TOOL/ }), { key: 'Enter' }) + expect(screen.getByRole('complementary', { name: 'Event details' })).toBeTruthy() expect(screen.getByText('Turn 1 · Step 1')).toBeTruthy() - expect(screen.getByText('完成')).toBeTruthy() + expect(screen.getByText('Completed')).toBeTruthy() + expect(screen.getByRole('tab', { name: 'Result' })).toBeTruthy() - fireEvent.click(screen.getByRole('tab', { name: '输入' })) - expect(screen.getByText('这条记录没有输入载荷')).toBeTruthy() - fireEvent.click(screen.getByRole('tab', { name: '输出' })) - expect(screen.getByText('[]')).toBeTruthy() - - fireEvent.click(screen.getByRole('button', { name: '关闭详情' })) - expect(screen.queryByRole('complementary', { name: '记录详情' })).toBeNull() + fireEvent.click(screen.getByRole('button', { name: 'Close details' })) + expect(screen.queryByRole('complementary', { name: 'Event details' })).toBeNull() }) it('waterfall renders bars and switching back to chat does not collapse it', async () => { @@ -226,7 +217,8 @@ describe('tab switching in ConversationRoot', () => { const b = await bench() mount(b.slots, []) fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' })) - expect(screen.getByText('暂无轨迹数据')).toBeTruthy() + expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy() + expect(screen.queryByRole('row')).toBeNull() expect(screen.queryByText(/turns ·/)).toBeNull() }) }) @@ -253,7 +245,8 @@ describe('span derivation', () => { expect(container.firstChild).toBeNull() render(createElement(TrajectoryView as FC, standaloneProps([]))) - expect(screen.getByText('暂无轨迹数据')).toBeTruthy() + expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy() + expect(screen.queryByRole('row')).toBeNull() }) }) @@ -261,7 +254,7 @@ describe('WaterfallView standalone branches', () => { it('empty window renders the placeholder copy', () => { render(createElement(WaterfallView as FC, standaloneProps([]))) - expect(screen.getByText('暂无瀑布数据')).toBeTruthy() + expect(screen.getByText('No timing data')).toBeTruthy() }) it('a turn without tool calls renders the node bar only', () => { @@ -361,7 +354,7 @@ describe('deriveSubSpans (waterfall lanes)', () => { const lane = view.container.querySelector('[data-subspan]') expect(lane).not.toBeNull() expect(lane!.textContent).toContain('bash') - expect(lane!.querySelector('[title*="1.80s"]')).not.toBeNull() + expect(lane!.querySelector('[title*="1.80 s"]')).not.toBeNull() expect(lane!.querySelector('[data-timing="measured"]')).not.toBeNull() }) diff --git a/packages/compact/compact-basic/tests/compact-basic.spec.ts b/packages/compact/compact-basic/tests/compact-basic.spec.ts index e73594678b..b444bb548d 100644 --- a/packages/compact/compact-basic/tests/compact-basic.spec.ts +++ b/packages/compact/compact-basic/tests/compact-basic.spec.ts @@ -1107,6 +1107,11 @@ describe('default one-shot summarizer', () => { expect(output).toEqual({ summary: [{ type: 'text', text: 'public summary' }], + rawOutput: [ + { type: 'reasoning', text: 'private' }, + { type: 'text', text: 'public summary' }, + { type: 'tool-call', id: CallId('unexpected'), name: 'x', arguments: '{}' }, + ], provider: MODEL, model: MODEL, maxTokens: 321,