fix(client): address trajectory review findings

This commit is contained in:
_Kerman
2026-07-28 15:07:44 +08:00
parent 64b9535c31
commit 9096dbb29d
9 changed files with 244 additions and 60 deletions

View File

@@ -1188,6 +1188,20 @@ function RecordPayload({
: 'No result captured'
if (!value) return <p className={css.noPayload}>{missing}</p>
const json = parseJsonContainer(value)
const singleTextResult = direction === 'output'
&& record.cell.outputBlocks?.length === 1
&& record.cell.outputBlocks[0]?.type === 'text'
if (singleTextResult && json !== undefined) {
return (
<JsonTree
data={json}
label="Result JSON"
className={preview ? css.jsonPreview : css.jsonPayload}
/>
)
}
if (
direction === 'output'
&& record.cell.outputBlocks?.some(block =>
@@ -1214,7 +1228,6 @@ function RecordPayload({
</div>
)
}
const json = parseJsonContainer(value)
if (json !== undefined) {
return (
<JsonTree

View File

@@ -5,7 +5,9 @@ import type { ConvViewProps } from '@deepseek-ai/dsh-client-ui-conversation/clie
import type {
AssistantMessageNode, ConversationContext, RequestView,
} from '@deepseek-ai/dsh-client-runtime/client'
import { deriveTrajectoryContextBranches } from './context-branches.ts'
import {
deriveTrajectoryContextBranches, trajectoryBranchContainsSeq,
} from './context-branches.ts'
import {
TrajectoryTable,
type TrajectoryRequestNumber,
@@ -97,9 +99,13 @@ export function TrajectoryView({ useSession, loadAllHistory }: ConvViewProps & T
)
const currentBranch = branches.at(-1)
if (currentBranch === undefined) throw new Error('trajectory branch projection must not be empty')
const selectedNodes = inspection === undefined || inspection.eventNodes.length === 0
? nodes
: inspection.eventNodes
const selectedNodes = currentBranch.nodes
const selectedRequests = useMemo(
() => requests.filter(request =>
trajectoryBranchContainsSeq(currentBranch, request.startSeq),
),
[currentBranch, requests],
)
const globalRequestNumbers = useMemo<readonly TrajectoryRequestNumber[]>(() => {
const assistantsByStep = new Map<string, AssistantMessageNode>()
for (const context of contexts) {
@@ -235,12 +241,12 @@ export function TrajectoryView({ useSession, loadAllHistory }: ConvViewProps & T
nodes: selectedNodes,
partial,
runningCalls,
requests,
requests: selectedRequests,
...(callSchemas === undefined ? {} : { callSchemas }),
codeDispatches,
}),
[
selectedNodes, partial, runningCalls, requests, callSchemas, codeDispatches,
selectedNodes, partial, runningCalls, selectedRequests, callSchemas, codeDispatches,
],
)
const collapsibleTurnIds = useMemo(

View File

@@ -96,6 +96,33 @@ describe('TrajectoryTable', () => {
expect(screen.getByText('ToolError: non_zero_exit')).toBeTruthy()
})
it('renders a single-text JSON tool result as a JSON tree', () => {
const turns: readonly TrajectoryTurnModel[] = [{
turn: 1,
groups: [{
title: 'Step 1',
cells: [{
index: 1,
kind: 'tool',
text: 'read {"path":"result.json"}',
outputDetail: '{"value":1,"nested":{"ok":true}}',
outputBlocks: [{
type: 'text',
content: '{"value":1,"nested":{"ok":true}}',
}],
timeSeconds: 0.1,
}],
}],
}]
render(<TrajectoryTable turns={turns} {...FOLD_PROPS} />)
fireEvent.click(screen.getByRole('row', { name: /TOOL/ }))
fireEvent.click(screen.getByRole('tab', { name: 'Result' }))
expect(screen.getByRole('tree', { name: 'Result JSON' })).toBeTruthy()
expect(screen.getByText('value:')).toBeTruthy()
})
it('keeps the first row and a compact summary when a turn is collapsed', () => {
render(
<TrajectoryTable

View File

@@ -16,7 +16,7 @@ import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type { UseSession } from '@deepseek-ai/dsh-client-web-react'
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
import type {
ConversationSnapshot, SessionId, SessionListState, WorkspaceListState,
ConversationSnapshot, RequestView, SessionId, SessionListState, WorkspaceListState,
} from '@deepseek-ai/dsh-client-runtime/client'
import type { ConvViewProps, ViewTab } from '@deepseek-ai/dsh-client-ui-conversation/client'
// Export discipline: packages/client/AGENTS.md.
@@ -270,6 +270,79 @@ describe('span derivation', () => {
})
})
describe('TrajectoryView branches', () => {
it('renders only the selected rewind branch while retaining session-global requests', () => {
const retained = {
kind: 'user',
seq: 1,
time: 1_000,
content: [{ type: 'text', text: 'retained user' }],
source: null,
} as unknown as ConversationSnapshot['nodes'][number]
const abandoned = {
kind: 'assistant',
seq: 3,
time: 3_000,
turn: 1,
step: 1,
blocks: [{ kind: 'text', text: 'abandoned response' }],
} as unknown as ConversationSnapshot['nodes'][number]
const current = {
kind: 'assistant',
seq: 5,
time: 5_000,
turn: 2,
step: 1,
blocks: [{ kind: 'text', text: 'current response' }],
} as unknown as ConversationSnapshot['nodes'][number]
const request = (startSeq: number, turn: number): RequestView => ({
purpose: 'assistant',
startSeq,
turn,
step: 1,
startedAt: startSeq * 1_000,
completedAt: startSeq * 1_000 + 100,
status: 'complete',
})
const store = createSnapshotStore({
nodes: [retained, current],
inspection: {
eventNodes: [retained, abandoned, current],
contexts: [
{ id: 0, nodes: [retained, abandoned] },
{
id: 1,
parentId: 0,
origin: 'rewind' as const,
originSeq: 4,
nodes: [retained, current],
},
],
requests: [request(2, 1), request(4, 2)],
callSchemas: new Map(),
},
openState: 'open' as const,
hasMore: false,
partial: null,
runningCalls: [] as ConversationSnapshot['runningCalls'],
codeDispatches: new Map(),
})
const view = render(
<TrajectoryView
{...standaloneProps([])}
useSession={bindSnapshotSelector(store) as unknown as UseSession<ConversationSnapshot>}
loadAllHistory={vi.fn(() => Promise.resolve())}
/>,
)
expect(screen.queryByText('abandoned response')).toBeNull()
expect(screen.getByText('current response')).toBeTruthy()
expect(screen.getByRole('row', { name: /Request 2, ASSISTANT/ })).toBeTruthy()
expect(view.container.querySelectorAll('[data-request-only="true"]')).toHaveLength(0)
})
})
describe('WaterfallView standalone branches', () => {
it('empty window renders the placeholder copy', () => {
render(createElement(WaterfallView as FC<ConvViewProps>,