fix(ui-trajectory): retain parallel tool interruptions
This commit is contained in:
@@ -9,6 +9,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import {
|
||||
deriveTrajectoryContextBranches, trajectoryBranchContainsRequest,
|
||||
trajectoryNodeIdentity,
|
||||
} from './context-branches.ts'
|
||||
import {
|
||||
TrajectoryTable,
|
||||
@@ -229,9 +230,9 @@ export function TrajectoryView({
|
||||
const currentBranch = branches.at(-1)
|
||||
if (currentBranch === undefined) throw new Error('trajectory branch projection must not be empty')
|
||||
const selectedNodes = useMemo(() => {
|
||||
const selected = new Map(currentBranch.nodes.map(node => [node.seq, node]))
|
||||
const selected = new Map(currentBranch.nodes.map(node => [trajectoryNodeIdentity(node), node]))
|
||||
for (const node of interruptedNodes) {
|
||||
selected.set(node.seq, node)
|
||||
selected.set(trajectoryNodeIdentity(node), node)
|
||||
}
|
||||
return [...selected.values()].sort((left, right) => left.seq - right.seq)
|
||||
}, [currentBranch.nodes, interruptedNodes])
|
||||
|
||||
@@ -23,11 +23,24 @@ interface MutableBranch {
|
||||
key: string
|
||||
contexts: ConversationContext[]
|
||||
latest: ConversationContext
|
||||
nodes: Map<number, ConversationNode>
|
||||
nodes: Map<string, ConversationNode>
|
||||
startSeq: number
|
||||
retainedSurfaceSeqs: Set<number>
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the identity used while coalescing one trajectory branch.
|
||||
* Synthetic tool interruptions share their closing boundary seq, so their
|
||||
* call ids distinguish parallel roots without inventing false event order.
|
||||
* @param node - projected conversation node.
|
||||
* @returns branch-local semantic identity.
|
||||
*/
|
||||
export function trajectoryNodeIdentity(node: ConversationNode): string {
|
||||
return node.kind === 'tool-result'
|
||||
? `tool-result\u0000${String(node.seq)}\u0000${node.callId}`
|
||||
: `seq\u0000${String(node.seq)}`
|
||||
}
|
||||
|
||||
function isCompactionCheckpoint(node: ConversationNode): boolean {
|
||||
if (node.kind !== 'context') return false
|
||||
const source = node.source
|
||||
@@ -73,7 +86,7 @@ export function deriveTrajectoryContextBranches(
|
||||
latest: context,
|
||||
nodes: new Map(
|
||||
[...inheritedNodes, ...context.nodes.filter(node => !isCompactionCheckpoint(node))]
|
||||
.map(node => [node.seq, node]),
|
||||
.map(node => [trajectoryNodeIdentity(node), node]),
|
||||
),
|
||||
startSeq: context.originSeq ?? Number.NEGATIVE_INFINITY,
|
||||
retainedSurfaceSeqs,
|
||||
@@ -85,7 +98,7 @@ export function deriveTrajectoryContextBranches(
|
||||
branch.contexts.push(context)
|
||||
branch.latest = context
|
||||
for (const node of context.nodes) {
|
||||
if (!isCompactionCheckpoint(node)) branch.nodes.set(node.seq, node)
|
||||
if (!isCompactionCheckpoint(node)) branch.nodes.set(trajectoryNodeIdentity(node), node)
|
||||
}
|
||||
}
|
||||
return mutable.map(branch => ({
|
||||
|
||||
Reference in New Issue
Block a user