diff --git a/apps/web/tests/navigation-panes.e2e.ts b/apps/web/tests/navigation-panes.e2e.ts index a4abc5cecd..311122780b 100644 --- a/apps/web/tests/navigation-panes.e2e.ts +++ b/apps/web/tests/navigation-panes.e2e.ts @@ -127,21 +127,18 @@ describe('web e2e: navigation & panes over a rich seeded session', () => { it.skipIf(MODE === 'record')('renders the trajectory ledger and opens its local record inspector', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-trajectory')) await page.getByRole('tab', { name: 'Trajectory' }).click() - // Thick row rules mark the two turns; compact inline markers identify - // each step without introducing dedicated group rows. + // Turn rules partition the ledger without restoring a separate header row. await expect.poll(() => page.locator('tr[data-turn-start="true"]').count(), { timeout: 15_000 }).toBe(2) - await expect.poll(() => page.locator('[aria-label^="Step "]').count(), { timeout: 10_000 }).toBe(3) - await expect.poll(() => page.getByRole('columnheader', { name: 'Tokens' }).count(), { timeout: 10_000 }).toBe(0) - await expect.poll(() => page.getByRole('columnheader', { name: '耗时' }).count(), { timeout: 10_000 }).toBe(0) + await expect.poll(() => page.getByRole('columnheader').count(), { timeout: 10_000 }).toBe(0) await page.locator('tr[data-kind="tool"]').first().click() - await expect.poll(() => page.getByRole('complementary', { name: '记录详情' }).count(), { timeout: 10_000 }).toBe(1) - await page.getByRole('tab', { name: '输出' }).click() + await expect.poll(() => page.getByRole('complementary', { name: 'Event details' }).count(), { timeout: 10_000 }).toBe(1) + await page.getByRole('tab', { name: 'Result' }).click() await expect.poll(() => page.getByText('NAVIGATION_OK', { exact: false }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1) const snapshot = (await captureStableAria(page, '[class*="viewArea"]', scaffold.workspaceCwd)) .split(SEED_ID).join('{{seededId}}') await compareOrRefreshGolden(TRAJECTORY_EXPECTED, snapshot, MODE) - await page.getByRole('complementary', { name: '记录详情' }) - .getByRole('button', { name: '关闭详情' }).click() + await page.getByRole('complementary', { name: 'Event details' }) + .getByRole('button', { name: 'Close details' }).click() }, 60_000) it.skipIf(MODE === 'record')('renders the waterfall tab with span stats and one lane per span', async () => { diff --git a/apps/web/tests/snapshots/navigation-panes/details-open.expected.md b/apps/web/tests/snapshots/navigation-panes/details-open.expected.md index d69a95eb2d..f93afb4c50 100644 --- a/apps/web/tests/snapshots/navigation-panes/details-open.expected.md +++ b/apps/web/tests/snapshots/navigation-panes/details-open.expected.md @@ -1,5 +1,6 @@ - text: bash - button "关闭详情" -- text: Input +- text: Input json +- button "复制" - code: "{ \"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\" }" - text: Output NAVIGATION_OK diff --git a/apps/web/tests/snapshots/navigation-panes/trajectory.expected.md b/apps/web/tests/snapshots/navigation-panes/trajectory.expected.md index 4224bce857..94a7690d48 100644 --- a/apps/web/tests/snapshots/navigation-panes/trajectory.expected.md +++ b/apps/web/tests/snapshots/navigation-panes/trajectory.expected.md @@ -1,57 +1,50 @@ -- toolbar "轨迹工具栏": - - text: 轨迹 - - strong: "8" - - text: 条记录 - - strong: "2" - - text: 轮 - - strong: "3" - - text: 次工具 - - button "收起记录" +- toolbar "Trajectory toolbar": + - text: Trajectory + - button "Collapse calls" + - button "Collapse turns" - table: - rowgroup: - - row "# 事件 内容": - - columnheader "#" - - columnheader "事件" - - columnheader "内容" - - rowgroup: - - 'row "记录 1,USER,NavScenario: first run bash to print exactly NAVIGATION_OK, then read nav-a.md and nav-b.md using two read calls in ONE assistant message, then reply with the single word FIRST_DONE and stop."': - - cell "#1" - - cell "USER" + - row "SYSTEM, Initial System Prompt": + - cell "SYSTEM" + - cell "Initial System Prompt" + - 'row "USER, NavScenario: first run bash to print exactly NAVIGATION_OK, then read nav-a.md and nav-b.md using two read calls in ONE assistant message, then reply with the single word FIRST_DONE and stop."': + - cell "Turn 1 USER" - 'cell "NavScenario: first run bash to print exactly NAVIGATION_OK, then read nav-a.md and nav-b.md using two read calls in ONE assistant message, then reply with the single word FIRST_DONE and stop."' - - row "记录 2,ASSISTANT,请求调用 bash、read×2": - - cell "#2" - - cell "Step 1 ASSISTANT": S1 ASSISTANT - - cell "请求调用 bash、read×2" - - 'row "记录 3,TOOL,bash · {\"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\"}" [selected]': - - cell "#3" + - 'row "Request 1, ASSISTANT, The user wants me to follow a specific navigation scenario. Let me: Run bash to print \"NAVIGATION_OK\" Read nav-a.md and nav-b.md in two read calls in ONE message Reply with \"FIRST_DONE\" Let me start with the bash command and the reads."': + - 'cell "Request #1 ASSISTANT"': + - 'button "Request #1"' + - text: ASSISTANT + - 'cell "The user wants me to follow a specific navigation scenario. Let me: Run bash to print \"NAVIGATION_OK\" Read nav-a.md and nav-b.md in two read calls in ONE message Reply with \"FIRST_DONE\" Let me start with the bash command and the reads."' + - 'row "TOOL, bash {\"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\"}" [selected]': - cell "TOOL" - - 'cell "bash · {\"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\"}→NAVIGATION_OK"' - - 'row "记录 4,TOOL,read · {\"file_path\": \"nav-a.md\"}"': - - cell "#4" + - 'cell "bash{\"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\"} → NAVIGATION_OK"' + - 'row "TOOL, read {\"file_path\": \"nav-a.md\"}"': - cell "TOOL" - - 'cell "read · {\"file_path\": \"nav-a.md\"}→{{cwd}}/nav-a.md file 1: # alpha nav (End of file - total 1 lines) "' - - 'row "记录 5,TOOL,read · {\"file_path\": \"nav-b.md\"}"': - - cell "#5" + - 'cell "read{\"file_path\": \"nav-a.md\"} → {{cwd}}/nav-a.md file 1: # alpha nav (End of file - total 1 lines) "' + - 'row "TOOL, read {\"file_path\": \"nav-b.md\"}"': - cell "TOOL" - - 'cell "read · {\"file_path\": \"nav-b.md\"}→{{cwd}}/nav-b.md file 1: # beta nav (End of file - total 1 lines) "' - - row "记录 6,ASSISTANT,FIRST_DONE": - - cell "#6" - - cell "Step 2 ASSISTANT": S2 ASSISTANT + - 'cell "read{\"file_path\": \"nav-b.md\"} → {{cwd}}/nav-b.md file 1: # beta nav (End of file - total 1 lines) "' + - row "Request 2, ASSISTANT, FIRST_DONE": + - 'cell "Request #2 ASSISTANT"': + - 'button "Request #2"' + - text: ASSISTANT - cell "FIRST_DONE" - - 'row "记录 7,USER,Reply in markdown with: a level-2 heading \"Navigation Summary\", a bulleted list of exactly two items, and a fenced code block containing echo WATERFALL. Then stop."': - - cell "#7" - - cell "USER" + - 'row "USER, Reply in markdown with: a level-2 heading \"Navigation Summary\", a bulleted list of exactly two items, and a fenced code block containing echo WATERFALL. Then stop."': + - cell "Turn 2 USER" - 'cell "Reply in markdown with: a level-2 heading \"Navigation Summary\", a bulleted list of exactly two items, and a fenced code block containing echo WATERFALL. Then stop."' - - 'row "记录 8,ASSISTANT,## Navigation Summary - alpha nav - beta nav ``` echo WATERFALL ```"': - - cell "#8" - - cell "Step 1 ASSISTANT": S1 ASSISTANT - - 'cell "## Navigation Summary - alpha nav - beta nav ``` echo WATERFALL ```"' -- complementary "记录详情": - - text: "TOOL 记录 #3 Turn 1 · Step 1" - - button "关闭详情" - - tablist "记录详情": - - tab "概览" - - tab "输入" - - tab "输出" [selected] - - tab "计时" - - tabpanel "输出": NAVIGATION_OK + - row "Request 3, ASSISTANT, Navigation Summary alpha nav beta nav echo WATERFALL": + - 'cell "Request #3 ASSISTANT"': + - 'button "Request #3"' + - text: ASSISTANT + - cell "Navigation Summary alpha nav beta nav echo WATERFALL" +- complementary "Event details": + - separator "Resize event details" + - text: TOOL Turn 1 · Step 1 + - button "Close details" + - tablist "Event details": + - tab "Summary" + - tab "Payload" + - tab "Result" [selected] + - tab "Schema" + - tab "Timing" + - tabpanel "Result": NAVIGATION_OK diff --git a/packages/client/runtime/src/client/sessions/fold-adapter.ts b/packages/client/runtime/src/client/sessions/fold-adapter.ts index 752ba4155c..d96cc2c16c 100644 --- a/packages/client/runtime/src/client/sessions/fold-adapter.ts +++ b/packages/client/runtime/src/client/sessions/fold-adapter.ts @@ -58,7 +58,6 @@ function materializeNode( return { kind: 'user', seq: event.seq, time: event.time, content: event.data.content, source: event.data.source, - meta: event.data.meta, } case 'assistant/message': return { @@ -76,7 +75,6 @@ function materializeNode( return { kind: 'steering', seq: event.seq, time: event.time, turn: event.data.turn, content: event.data.content, source: event.data.source, - meta: event.data.meta, } case 'tool/result': { const call = callIndex.get(String(event.data.callId)) diff --git a/packages/client/runtime/src/client/sessions/session.ts b/packages/client/runtime/src/client/sessions/session.ts index cc79727f69..5006aa0447 100644 --- a/packages/client/runtime/src/client/sessions/session.ts +++ b/packages/client/runtime/src/client/sessions/session.ts @@ -955,7 +955,7 @@ function deriveModelRequests(events: readonly SessionEvent[]): readonly ModelReq const key = modelRequestKey(sourceEvent.data.turn, sourceEvent.data.step) const index = byStep.get(key) const request = index === undefined ? undefined : requests[index] - if (request !== undefined && request.status === 'running') { + if (index !== undefined && request !== undefined && request.status === 'running') { requests[index] = { ...request, completedAt: sourceEvent.time, diff --git a/packages/client/ui-conversation/tests/skeleton.spec.tsx b/packages/client/ui-conversation/tests/skeleton.spec.tsx index b777c85ac3..f0c5288b00 100644 --- a/packages/client/ui-conversation/tests/skeleton.spec.tsx +++ b/packages/client/ui-conversation/tests/skeleton.spec.tsx @@ -88,6 +88,7 @@ function mount( if (key === 'conversation.session') { return ( children(SID)} useSession={useSession} diff --git a/packages/client/ui-primitives/src/JsonTree.tsx b/packages/client/ui-primitives/src/JsonTree.tsx index c3ce0aeda5..bad1b4b4c9 100644 --- a/packages/client/ui-primitives/src/JsonTree.tsx +++ b/packages/client/ui-primitives/src/JsonTree.tsx @@ -72,7 +72,10 @@ function previewPrimitive(value: unknown): ReactNode { if (typeof value === 'symbol') { return {value.description ?? 'Symbol'} } - return {value.name || 'Function'} + if (typeof value === 'function') { + return {value.name || 'Function'} + } + return null } function previewValue(value: unknown, depth: number): ReactNode { diff --git a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx index 17ce682b0e..5751471ae5 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryTable.tsx @@ -1147,7 +1147,7 @@ function RequestTiming({ }: { assistant: TableRecord | undefined anchor: TableRecord | undefined - request?: TrajectoryRequestNumber + request: TrajectoryRequestNumber | undefined }) { if (assistant !== undefined) return if (request?.startedAt !== undefined) { @@ -1746,8 +1746,9 @@ export function TrajectoryTable({ onPointerDown={(event) => { if (event.button !== 0) return const details = event.currentTarget.parentElement - const split = details?.parentElement - if (details === null || split === null) return + if (details === null) return + const split = details.parentElement + if (split === null) return const splitWidth = split.getBoundingClientRect().width detailsResizeDrag.current = { pointerId: event.pointerId, @@ -1785,8 +1786,9 @@ export function TrajectoryTable({ onKeyDown={(event) => { if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return const details = event.currentTarget.parentElement - const split = details?.parentElement - if (details === null || split === null) return + if (details === null) return + const split = details.parentElement + if (split === null) return const direction = event.key === 'ArrowLeft' ? 1 : -1 const currentDetailsWidth = details.getBoundingClientRect().width const splitWidth = split.getBoundingClientRect().width diff --git a/packages/client/ui-trajectory/src/client/layout.ts b/packages/client/ui-trajectory/src/client/layout.ts index eb996f05ed..d6b0ec0edc 100644 --- a/packages/client/ui-trajectory/src/client/layout.ts +++ b/packages/client/ui-trajectory/src/client/layout.ts @@ -331,7 +331,6 @@ export function deriveTrajectoryLayout(input: TrajectoryLayoutInput): readonly T text: summarizeContent(node.content), sourceSeq: node.seq, messageSource: node.source, - ...(node.meta === undefined ? {} : { messageMeta: node.meta }), inputDetail: detailContent(node.content), sourceBlocks: node.content.map(block => sourceBlock(block)), timeSeconds: 0, diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index be64f5c79f..e51356c854 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -130,6 +130,7 @@ function mount(slots: SlotsService, nodes: ConversationSnapshot['nodes'] = NODES }) as unknown as ConversationSessionProps['renderSlot'] return render( children(SID)} useSession={useSession} diff --git a/tsconfig.client.json b/tsconfig.client.json index 9f67ab4af7..dd43357d92 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -20,6 +20,7 @@ "packages/client/*/tests/**/*.ts", "packages/client/*/tests/**/*.tsx", "packages/client/tsdown.client.ts", + "scripts/client-bundle-css.spec.ts", "scripts/client-bundle-purity.spec.ts" ], "references": [ diff --git a/tsconfig.host.json b/tsconfig.host.json index 9cf2a86bda..cf10b76791 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -32,6 +32,7 @@ ], "exclude": [ "packages/client/**", + "scripts/client-bundle-css.spec.ts", "scripts/client-bundle-purity.spec.ts" ], "references": [