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": [