mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(client): address remaining conversation review feedback
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.md
|
||||
2026-08-09-client-conversation-node-assembly.md: 9952f8fac0f2a3b0fe72cc97ae9a8fb4f96ace08
|
||||
2026-08-09-client-conversation-node-assembly.zh.md: f27a82459cdacdb4a2da27cf12b30c93f2dfdf64
|
||||
2026-08-09-client-conversation-node-assembly.md: 16a39539064644e5467f701789a7e2ef1f7ff172
|
||||
2026-08-09-client-conversation-node-assembly.zh.md: 0e0fbdf8f3320393022528e6e3fe2cf0d492a1d3
|
||||
|
||||
@@ -394,6 +394,8 @@ History-path tests cover complete replace, non-overlapping prepend, overlapping-
|
||||
|
||||
A new business node can register its matcher, State transitions, optional Location data, final target Node, and renderer locally without changing Session's business switch. `ChatNodeDataMap` and the Location data maps let a business package merge strongly typed data into the contract; every related Event must still expose a stable ID derivable from that Event alone.
|
||||
|
||||
Host business packages declaration-merge their durable Event members into `@deepseek-ai/dsh-session/types`, while Client Definitions type-only import the corresponding business package `/types` subpaths. Augmenting the declaring interface rather than a re-export barrel gives the independent Host and Client TypeScript programs the same Event narrowing without pulling Host runtime into the Client graph.
|
||||
|
||||
Initial tail, older prepend, and live append share one set of Context invariants. Missing starts, Reader window gaps, unknown Locations, and high-frequency deltas are explicit engine states and require no direction-specific business cache.
|
||||
|
||||
Append does not scan historical Contexts; prepend replays only Contexts whose Matches, Locations, or Reader answers actually changed. A structural Chat change may still recompute visible order and indexes, but does not rerun unrelated business folds or replace unchanged Node identity.
|
||||
|
||||
@@ -394,6 +394,8 @@ Assembled Web snapshot、GUI 和浏览器场景覆盖真实 plugin graph。浏
|
||||
|
||||
新增业务节点可以局部注册自己的 matcher、State 转换、可选 Location data、最终 target Node 和 renderer,不再修改 Session 的业务 switch。`ChatNodeDataMap` 和 Location data maps 允许业务 package 通过 declaration merging 合入强类型 data;所有相关 Event 仍须暴露可单 Event 推导的稳定 ID。
|
||||
|
||||
Host 业务 package 把自己的持久 Event 成员 declaration-merge 到 `@deepseek-ai/dsh-session/types`,Client Definition 则通过对应业务 package 的 `/types` 子路径进行 type-only import。增强实际声明接口而不是重导出 barrel,使 Host 和 Client 的独立 TypeScript Program 都能获得相同的 Event narrowing,同时不把 Host runtime 带入 Client 图。
|
||||
|
||||
初始尾页、older prepend 和 live append 共享一套 Context 不变量。缺 start、Reader window gap、Location unknown 以及高频 delta 都是引擎明确表达的状态,不需要业务另建方向相关 cache。
|
||||
|
||||
Append 不扫描历史 Context;prepend 只 replay Match、Location 或 Reader 答案真正受影响的 Context。Chat 结构变化仍可能重算 visible order 和索引,但不会重跑无关业务 fold 或替换未变化 Node identity。
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/compaction.md
|
||||
compaction.md: 1aca48d677a83a488d9590edad3f8a68ca662b70
|
||||
compaction.zh.md: e2cfabc0891126d9f2165e47dffcf485f4a8a339
|
||||
compaction.md: 11ee6eae797db1c57bf2ffbb90f1f37f47b8400c
|
||||
compaction.zh.md: a560e47dbb7aa92f0757179534803bbcaaebc6ba
|
||||
|
||||
@@ -20,7 +20,7 @@ The lock brackets the **whole** operation: `compact/start` is appended first, th
|
||||
|
||||
The markers are lock time points, not an exclusive container. An unrelated idle injection can appear between a standalone manual start and end while summarization is pending. The manual path revalidates only its selected positional span, so that injected context survives after the replacement checkpoint. A live unmatched start blocks every entry point; an unmatched start before a newer `session/end-seed` is stale evidence from a prior lifecycle and is ignored.
|
||||
|
||||
These variants are merged inside a `declare module '@deepseek-ai/dsh-session'` block, so — unlike the top-level types on the other subsystem pages — they are not pasted as a drift-checked ` ```ts type-equiv ` block (the `verify-type-equiv` extractor matches only top-level declarations by name). The payload table above is the catalog entry; follow the source link for the authoritative shapes.
|
||||
These variants are merged inside a `declare module '@deepseek-ai/dsh-session/types'` block, so — unlike the top-level types on the other subsystem pages — they are not pasted as a drift-checked ` ```ts type-equiv ` block (the `verify-type-equiv` extractor matches only top-level declarations by name). The payload table above is the catalog entry; follow the source link for the authoritative shapes.
|
||||
|
||||
## `CompactionResult`
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
这些标记表示锁的时间点,而不是排他的容器。摘要等待期间,不相关的空闲注入可以出现在独立的手动 start 与 end 之间。手动路径只重新验证所选位置 span,因此替换检查点之后仍保留该注入上下文。活动的未匹配 start 会阻塞所有入口点;较新 `session/end-seed` 之前的未匹配 start 是先前生命周期留下的陈旧证据,会被忽略。
|
||||
|
||||
这些变体在 `declare module '@deepseek-ai/dsh-session'` 块内合并,因此——与其他子系统页面上的顶层类型不同——它们不以漂移检查的 ` ```ts type-equiv ` 块粘贴(`verify-type-equiv` 提取器只按名称匹配顶层声明)。上方的载荷表即为目录条目;权威形状请循源码链接查看。
|
||||
这些变体在 `declare module '@deepseek-ai/dsh-session/types'` 块内合并,因此——与其他子系统页面上的顶层类型不同——它们不以漂移检查的 ` ```ts type-equiv ` 块粘贴(`verify-type-equiv` 提取器只按名称匹配顶层声明)。上方的载荷表即为目录条目;权威形状请循源码链接查看。
|
||||
|
||||
## `CompactionResult`
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ type TurnTailNodeViewProps = ChatNodeViewProps<'turn-tail'> & PropsRenderSlots<'
|
||||
|
||||
/** Turn-local actions and feature tail over the Location index, independent of Assistant placement. */
|
||||
export const TurnTailNodeView = memo(function TurnTailNodeView({
|
||||
node, openFile, forkAt, renderSlotChain, t,
|
||||
node, openFile, forkAt, renderSlotChain, t, useSession,
|
||||
}: TurnTailNodeViewProps) {
|
||||
const data = node.data
|
||||
const hasLaterChatNode = useSession(snapshot =>
|
||||
snapshot.chat.locations.getTurn(data.turn).at(-1) !== node.key)
|
||||
const turn = node.location.kind === 'turn' || node.location.kind === 'step'
|
||||
? node.location.turn
|
||||
: undefined
|
||||
@@ -34,7 +36,7 @@ export const TurnTailNodeView = memo(function TurnTailNodeView({
|
||||
tokensPerSecond={data.tokensPerSecond}
|
||||
clock="end"
|
||||
onBranch={() => { forkAt(closing.finalNode.seq) }}
|
||||
branchUnavailable={data.branchUnavailable}
|
||||
branchUnavailable={data.branchUnavailable || hasLaterChatNode}
|
||||
className={css.actions}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface TurnTailChatData {
|
||||
readonly time: number
|
||||
/** Last finalized content-bearing Assistant in this Turn. */
|
||||
readonly closing: FinalAssistantChatData | null
|
||||
/** Whether later Assistant/Step material makes the closing seq non-tail. */
|
||||
/** Whether non-rendered later evidence makes the closing seq non-tail. */
|
||||
readonly branchUnavailable: boolean
|
||||
readonly ttftMs?: number
|
||||
readonly tokensPerSecond?: number
|
||||
|
||||
@@ -181,14 +181,12 @@ function makeHarness(init?: Partial<ConversationSnapshot>) {
|
||||
if (key !== 'conversation.chat.node') return opts?.fallback ?? null
|
||||
const nodeOwner = owner as RoutedChatNodeOwner
|
||||
const nodeKey = opts?.hookContext as string | undefined
|
||||
const useTurnData = ((dataKey: string) => {
|
||||
return props.useSession((snapshot) => {
|
||||
const location = nodeKey === undefined ? undefined : snapshot.chat.nodes.get(nodeKey)?.location
|
||||
return location?.kind === 'turn' || location?.kind === 'step'
|
||||
? location.turn.data.get(dataKey as never)
|
||||
: undefined
|
||||
})
|
||||
}) as UseChatNodeTurnData
|
||||
const useTurnData: UseChatNodeTurnData = dataKey => props.useSession((snapshot) => {
|
||||
const location = nodeKey === undefined ? undefined : snapshot.chat.nodes.get(nodeKey)?.location
|
||||
return location?.kind === 'turn' || location?.kind === 'step'
|
||||
? location.turn.data.get(dataKey)
|
||||
: undefined
|
||||
})
|
||||
const nodeProps = <Kind extends ChatNode['kind']>(): ChatNodeViewProps<Kind> => (
|
||||
{ ...props, ...nodeOwner, useTurnData } as unknown as ChatNodeViewProps<Kind>
|
||||
)
|
||||
@@ -722,6 +720,28 @@ describe('ChatView', () => {
|
||||
expect(h.forkAt.mock.calls).toEqual([[2]])
|
||||
})
|
||||
|
||||
it('disables fork when the indexed Turn has a later steering Node', () => {
|
||||
const base = chatSnapshotFixture({
|
||||
nodes: [user(1, 'question'), assistant(2, 'answer')],
|
||||
turnEnds: new Map([[1, 4]]),
|
||||
})
|
||||
const chat = {
|
||||
...base,
|
||||
locations: {
|
||||
getTurn: (turn: number) => turn === 1
|
||||
? [...base.locations.getTurn(turn), 'fixture:steering:later']
|
||||
: base.locations.getTurn(turn),
|
||||
getStep: (turn: number, step: number) => base.locations.getStep(turn, step),
|
||||
},
|
||||
}
|
||||
const h = makeHarness({ chat })
|
||||
const view = render(<h.ChatView {...h.props} />)
|
||||
const branch = view.getByRole('button', { name: '在新对话中分支' })
|
||||
expect(branch.getAttribute('aria-disabled')).toBe('true')
|
||||
fireEvent.click(branch)
|
||||
expect(h.forkAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps final content actions but disables branch when Tool and interrupted Think follow it', () => {
|
||||
const interruptedThink: AssistantMessageNode = {
|
||||
kind: 'assistant', seq: 4.1, time: 4_100, turn: 1, step: 2,
|
||||
|
||||
@@ -36,7 +36,9 @@ interface DeliverablesState extends DeliverablesTurnData {
|
||||
* Paths a call view reports having created or changed, by render intent rather
|
||||
* than tool name: a diff card, or a generic card whose kind is `edit` (the
|
||||
* shape `str_replace_editor`'s insert presents). Every other card produces
|
||||
* nothing to open — a read looked, a delete removed, a terminal ran.
|
||||
* nothing to open — a read looked, a delete removed, a terminal ran. Only
|
||||
* root call views enter this Turn accumulator; nested Code Mode dispatches
|
||||
* preserve the pre-assembly behavior and do not contribute independently.
|
||||
*/
|
||||
function producedPaths(view: ToolResultNode['callView']): readonly string[] {
|
||||
if (view === null) return []
|
||||
|
||||
@@ -211,6 +211,7 @@ export default defineConfig({
|
||||
'packages/client/ui-workspace/src/client/index.ts',
|
||||
'packages/client/test-runtime/src/translate.ts',
|
||||
'packages/client/ui-primitives/src/JsonTree.tsx',
|
||||
'packages/client/ui-deliverables/src/client/turn-deliverables.ts',
|
||||
// Typert generator: correctness is pinned by its fixture suites and
|
||||
// the byte-for-byte catalog reproduction test; per-file coverage
|
||||
// would put whole-workspace compiler analysis under v8
|
||||
|
||||
Reference in New Issue
Block a user