fix(client): address conversation assembly 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 packages/client/runtime/README.md
|
||||
README.md: b2bb06e50ecd791d74cb609404dd219e5a21913e
|
||||
README.zh.md: 72cd99ac765875c828cc9163d978e0b8fb7f44e4
|
||||
README.md: 46a383f22032c8da7e4bb9b5fb445980661c670f
|
||||
README.zh.md: 73578a8f249472abfdd7c539e3eaee6de767ad60
|
||||
|
||||
@@ -38,7 +38,7 @@ SlotsService gives the renderer separate bare observables for `useSessions` and
|
||||
|
||||
Each `Session` gives its contiguous event window to a `ConversationNodeAssembler`. Plugins register business Definitions that map one event to a stable `{kind, id}`, create State at the unique start event, fold correlated updates, and build final nodes for registered view targets. The assembler owns the Context index, read-only predecessor lookup, and a reference-stable Turn/Step Location index. A live append evaluates each Definition once and updates only the matched Context; loading an older page preserves existing Context and node identities, matches only the newly prepended events, and replays Contexts whose predecessor or Location facts changed. Full replacement is reserved for open, resync, and gap repair.
|
||||
|
||||
`ui-conversation` registers the built-in Chat Definitions and the keyed Chat snapshot builder. Append-origin user, assistant, and Tool results remain the human record; model-only replacement copies stay out, except that a compaction checkpoint becomes its own marker and resolves missing summary provenance when an older page supplies it. Durable inbox splice Contexts classify next-step user messages as steering without making inbox state a Session special case. Context messages retain producer provenance and form. `ConversationSnapshot.nodes`, `partial`, and `runningCalls` are compatibility slices derived from the same materialized Chat nodes for consumers that have not moved to `ConversationSnapshot.chat`; Session does not run a second business fold.
|
||||
`ui-conversation` registers the built-in Chat Definitions and the keyed Chat snapshot builder. Append-origin user, assistant, and Tool results remain the human record; model-only replacement copies stay out, except that a compaction checkpoint becomes its own marker and resolves missing summary provenance when an older page supplies it. Durable inbox splice Contexts classify next-step user messages as steering without making inbox state a Session special case. Context messages retain producer provenance and form. StatsLine reads `ConversationSnapshot.chat.legacy.nodes`, while Session mirrors that legacy slice into the top-level `nodes`, `partial`, and `runningCalls` public compatibility fields without running a second business fold. Trajectory consumes neither compatibility surface; its activated `session-history` inspection keeps an independent fold until it gains its own registered target.
|
||||
|
||||
The Chat builder keeps one mutable keyed store per Session. Content updates notify only the affected node key, structural changes rebuild order and Location membership, and a prepend adds rows without replacing existing keyed values. Assistant chunks update Definition State for every event but request at most one materialization per animation frame; final messages and Turn/Step closure publish immediately. See the [client Tool presentation decision](../../../.agents/notes/implemented/architecture/2026-08-08-client-tool-presentation-ownership.md).
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ SlotsService 分别为 renderer 提供 `useSessions` 与 `useWorkspaces` 的裸
|
||||
|
||||
每个 `Session` 都把连续事件窗口交给 `ConversationNodeAssembler`。插件注册业务 Definition,把单个事件映射为稳定的 `{kind, id}`,在唯一 start 事件处创建 State,折叠有关联的 update,再为已注册的视图目标构造最终节点。Assembler 负责 Context 索引、只读前序 Context 查询,以及引用稳定的 Turn/Step Location 索引。实时 append 只对每个 Definition 求值一次,并且只更新命中的 Context;加载更早分页时保留已有 Context 与节点身份,只匹配新 prepend 的事件,并重放前序依赖或 Location 事实发生变化的 Context。完整替换仅用于 open、resync 和 gap repair。
|
||||
|
||||
`ui-conversation` 注册内建 Chat Definition 与 keyed Chat snapshot builder。append 来源的 user、assistant 和 Tool result 构成人类可见记录;仅供模型使用的 replacement 副本不进入 Chat,compaction 检查点除外,它会成为独立标记,并在更早分页补齐 summary 溯源后更新。持久 inbox splice Context 能把 next-step 用户消息判定为 steering,无须让 inbox 状态成为 Session 特例。上下文消息保留生产者 provenance 与 form。`ConversationSnapshot.nodes`、`partial` 和 `runningCalls` 是从同一批已物化 Chat 节点派生的兼容切片,供尚未迁移到 `ConversationSnapshot.chat` 的消费者使用;Session 不再运行第二套业务 fold。
|
||||
`ui-conversation` 注册内建 Chat Definition 与 keyed Chat snapshot builder。append 来源的 user、assistant 和 Tool result 构成人类可见记录;仅供模型使用的 replacement 副本不进入 Chat,compaction 检查点除外,它会成为独立标记,并在更早分页补齐 summary 溯源后更新。持久 inbox splice Context 能把 next-step 用户消息判定为 steering,无须让 inbox 状态成为 Session 特例。上下文消息保留生产者 provenance 与 form。StatsLine 读取 `ConversationSnapshot.chat.legacy.nodes`;Session 则把该 legacy slice 镜像到顶层 `nodes`、`partial` 和 `runningCalls` 公共兼容字段,无须运行第二套业务 fold。Trajectory 不消费这两种兼容表面;在它获得独立注册 target 之前,已激活的 `session-history` inspection 继续维护独立 fold。
|
||||
|
||||
Chat builder 为每个 Session 保留一个 mutable keyed store。内容更新只通知受影响的 node key;结构变化才重建顺序和 Location 成员关系;prepend 只增加行,不替换既有 keyed value。每个 Assistant chunk 都会更新 Definition State,但最多每个 animation frame 请求一次物化;final message 与 Turn/Step 关闭会立即发布。参见 [Client Tool 展示所有权决策](../../../.agents/notes/implemented/architecture/2026-08-08-client-tool-presentation-ownership.md)。
|
||||
|
||||
|
||||
@@ -743,6 +743,8 @@ export class ConversationNodeAssembler {
|
||||
context.locationData[scope] = data
|
||||
if (data !== null) entries.push({ owner: context.key, data })
|
||||
}
|
||||
// Turn publishers may read Step data from this same flush, so each phase
|
||||
// installs the cumulative replacement before the next phase builds.
|
||||
this.locationIndex.replaceData(entries)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// ConversationSnapshot / ConversationNode: the only data shape the logic layer feeds the UI.
|
||||
// Immutability contract: every change swaps the top-level object; unchanged
|
||||
// substructures keep their references (the React.memo premise). callId/approvalId stay plain
|
||||
// string here (narrow to real brands when convenient).
|
||||
// Publication contract: every change swaps the top-level object; unchanged
|
||||
// substructures keep their references (the React.memo premise). Chat node and
|
||||
// Location stores are stable live readers, so old snapshots are not time-point
|
||||
// views. callId/approvalId stay plain string here (narrow to real brands when
|
||||
// convenient).
|
||||
|
||||
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
@@ -336,7 +338,10 @@ export interface PromptError {
|
||||
error: RpcError
|
||||
}
|
||||
|
||||
/** Stable per-key reader for final Chat view Nodes. */
|
||||
/**
|
||||
* Stable live per-key reader. An old ChatSnapshot observes later flushes
|
||||
* through this store.
|
||||
*/
|
||||
export interface ChatNodeStore {
|
||||
/** @param key - stable Conversation Context key. @returns current Node, when visible or hidden. */
|
||||
get(key: string): ChatConversationViewNode | undefined
|
||||
@@ -344,7 +349,10 @@ export interface ChatNodeStore {
|
||||
values(): readonly ChatConversationViewNode[]
|
||||
}
|
||||
|
||||
/** Stable per-Location membership index for turn-local and step-local consumers. */
|
||||
/**
|
||||
* Stable live Location index. An old ChatSnapshot observes later membership
|
||||
* changes through this index.
|
||||
*/
|
||||
export interface ChatLocationNodeIndex {
|
||||
/** @param turn - owning turn. @returns ordered Chat Node keys in the turn. */
|
||||
getTurn(turn: number): readonly string[]
|
||||
@@ -352,7 +360,7 @@ export interface ChatLocationNodeIndex {
|
||||
getStep(turn: number, step: number): readonly string[]
|
||||
}
|
||||
|
||||
/** Temporary projection consumed by Trajectory and unmigrated readers. */
|
||||
/** Compatibility projection backing StatsLine and the legacy top-level snapshot fields. */
|
||||
export interface LegacyConversationSlice {
|
||||
readonly nodes: readonly ConversationNode[]
|
||||
readonly turnTimings: ReadonlyMap<number, { readonly startTime: number; readonly endTime?: number }>
|
||||
@@ -361,7 +369,7 @@ export interface LegacyConversationSlice {
|
||||
readonly runningCalls: readonly RunningToolCall[]
|
||||
}
|
||||
|
||||
/** Incremental Chat target snapshot: stable keyed stores plus structural order. */
|
||||
/** Incremental Chat publication with immutable order and stable live keyed readers. */
|
||||
export interface ChatSnapshot {
|
||||
readonly order: readonly string[]
|
||||
readonly nodes: ChatNodeStore
|
||||
@@ -399,7 +407,7 @@ export interface ConversationSnapshot {
|
||||
sessionId: SessionId
|
||||
/** Final Chat target assembled from independently registered business Definitions. */
|
||||
chat: ChatSnapshot
|
||||
/** Legacy Trajectory slice derived from the registered Chat Definitions. */
|
||||
/** Legacy top-level compatibility field mirrored from the registered Chat Definitions. */
|
||||
nodes: readonly ConversationNode[]
|
||||
/** Exact in-window `turn/start` time and optional matching `turn/end` time. */
|
||||
turnTimings: ReadonlyMap<number, { readonly startTime: number; readonly endTime?: number }>
|
||||
|
||||
@@ -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 packages/client/ui-conversation/README.md
|
||||
README.md: 911bca28dcfb31b1d8ef9ea5458a0d2017d8b31d
|
||||
README.zh.md: 25574421bbcc3992188e378a49a69acf25744af7
|
||||
README.md: 3570e814609af0bc4e7d048a55205af756fae843
|
||||
README.zh.md: e37f867c72d83b47924e87261e6ee5ff8b374a6f
|
||||
|
||||
@@ -20,7 +20,7 @@ Logged non-user messages render as a default-collapsed disclosure whose header n
|
||||
|
||||
A Think row stays collapsed by default and exposes live reasoning throughput without expanding the chain of thought: while its reasoning block is the streaming tail, the summary switches from the settled first line to the latest non-blank line and its one-line scrollport follows each delta to the inline end. Expanding the row removes the moving summary and leaves the full reasoning in ordinary page flow, so page reading never fights an internal follower; settlement restores the stable first-line summary at the left edge ([decision](../../../.agents/notes/implemented/feature/2026-08-02-web-thinking-tail-scroll.md)).
|
||||
|
||||
The chat view keeps Tool placement but delegates Tool presentation. It passes each ordered root call through `conversation.chat.tool`, and the details shell passes the selected call through `conversation.details.tool`. The assembled Web bundle fills the whole-Tool seat with [`ui-tool`](../ui-tool/README.md), which selects Runtime-projected Code Dispatch children and owns root/child composition, per-name dispatch, generic rendering, and render-intent cards; the details seat alone retains a raw-result fallback when that renderer is absent.
|
||||
The chat view keeps Tool placement but delegates Tool presentation. Each ordered `tool-call` Conversation Node dispatches through the matching key of `conversation.chat.node`, while the details shell passes the selected call through `conversation.details.tool`. The assembled Web bundle registers [`ui-tool`](../ui-tool/README.md) for that Chat Node key; it renders the Runtime-projected recursive root/child tree and owns per-name dispatch, generic rendering, and render-intent cards. The details seat alone retains a raw-result fallback when that renderer is absent.
|
||||
|
||||
The chat flow projects consecutive model-retry nodes across retry turns into one stable, muted status row updated to the latest attempt; every retry event remains in the runtime snapshot and session log. Its frontend countdown anchors the scheduled delay to client receipt, avoiding host/browser clock skew, rounds remaining time up to seconds, and has a one-second floor. The latest unresolved retry uses a left-to-right text shimmer. Subsequent turn facts distinguish an attempt that started from one cancelled during backoff, while the Host running bit only controls the live animation; the row then shows a static completed or cancelled label. Normal policy rows show the finite retry maximum; always policy rows show `∞`. Activating the row reveals the latest exact retry delay and failure message. The client runtime removes each failed step's streaming tail before its retry node arrives, while the status remains visible after a later attempt succeeds. An unretried terminal failure renders as a persistent inline status at its turn boundary, showing the display-safe durable message and optional error code without offering an action the Host cannot fulfill; AUTH copy never echoes provider-supplied credential fragments.
|
||||
|
||||
@@ -40,7 +40,7 @@ The chat stats line takes its token accounting from the generic token-meter `tok
|
||||
|
||||
`src/client/` is organized by domain. `contract/` is the shared face for slot declarations, composed props, and cross-domain types; `skeleton/`, `chat/`, `input/`, `queue/`, and `settings/` keep their implementations internal, while `apply.ts` is their assembly point. The `/client` export surface contains only loader entries, service classes, and contract types; components and store factories reach the page through slot registrations.
|
||||
|
||||
A finished turn ends with a turn-tail hole: the chat view renders the `conversation.chat.turnTail` list slot between the closing assistant's body and its IconActions, once per turn at the seq `assistantActionsSeqs` elects, dispatching `TurnTailOwnerProps` (the snapshot nodes, the closing seq, and the tool rows' `openFile`). This package owns only the hole; the produced-files row that fills it — derivation from the mutation tools' `locations`, the chip cap, the copy — lives in `@deepseek-ai/dsh-client-ui-deliverables`, so composing that plugin out of cordis.yml turns the surface off while the hole renders empty at zero cost. The closing prose participates through the same off switch: the chat view asks the optional `chatFileMentions` service (ctx.get; provided by the same plugin) for a closing message's inline-code vocabulary and threads the result into MarkdownText's `fileMentions` contract — an absent service leaves the prose inert.
|
||||
A finished turn materializes one ordered `turn-tail` Conversation Node. Its engine-owned `TurnLocation` supplies the closing Assistant and Turn data; the renderer places the `conversation.chat.turnTail` chain before that node's IconActions and dispatches `TurnTailOwnerProps` containing the Turn, closing seq, and `openFile`. This package owns only the hole; `@deepseek-ai/dsh-client-ui-deliverables` accumulates mutation-tool `locations` into Turn data and owns the produced-files row, chip cap, and copy, so composing that plugin out of cordis.yml turns the surface off while the hole renders empty at zero cost. The closing prose participates through the same off switch: the chat view asks the optional `chatFileMentions` service (ctx.get; provided by the same plugin) for a closing message's inline-code vocabulary and threads the result into MarkdownText's `fileMentions` seam — an absent service leaves the prose inert.
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
Think 行默认保持折叠,并在不展开思维链的情况下暴露实时推理(reasoning)吞吐:当推理块是流式输出尾部时,摘要从结算后的首行切换到最新的非空行,其单行滚动区会随每个 delta 追到行内末端。展开该行会移除移动摘要,让完整推理进入普通页面流,因此页面阅读不会与内部跟随器争夺滚动;结算后恢复左对齐的稳定首行摘要([决策](../../../.agents/notes/implemented/feature/2026-08-02-web-thinking-tail-scroll.md))。
|
||||
|
||||
聊天视图保留 Tool 的消息流位置,但委托其展示。它通过 `conversation.chat.tool` 传递每个已排序的 root call;详情壳层则通过 `conversation.details.tool` 传递当前选中的调用。组装后的 Web bundle 由 [`ui-tool`](../ui-tool/README.md) 填充整体 Tool 席位,并由后者选择 Runtime 已投影的 Code Dispatch 子调用,负责 root/child 编排、按名称分发、通用展示和 render-intent 卡片;只有详情席位会在该 renderer 缺席时保留 raw-result fallback。
|
||||
聊天视图保留 Tool 的消息流位置,但委托其展示。每个已排序的 `tool-call` Conversation Node 都通过 `conversation.chat.node` 的同名 key 分发;详情壳层则通过 `conversation.details.tool` 传递当前选中的调用。组装后的 Web bundle 为该 Chat Node key 注册 [`ui-tool`](../ui-tool/README.md),由后者渲染 Runtime 已投影的递归 root/child 树,并负责按名称分发、通用展示和 render-intent 卡片;只有详情席位会在该 renderer 缺席时保留 raw-result fallback。
|
||||
|
||||
聊天流会将跨重试轮次连续出现的模型重试节点投影为一个稳定的弱化状态行,并用最新一次尝试更新该行;每个重试事件仍保留在运行时快照与会话日志中。前端倒计时以客户端收到事件的时刻为计划延迟的起点,避免 Host 与浏览器的时钟偏差;剩余时间向上取整到秒,且下限为 1 秒。最近一次尚未完成的重试会显示从左到右的文字渐变动画。后续轮次事实用于区分已开始的尝试与在退避期间取消的尝试,Host 的 running 位只控制实时动画;随后该行会显示静态的已完成或已取消标签。normal 策略行显示有限重试上限;always 策略行显示 `∞`。激活该行会显示最近一次重试的精确延迟和失败消息。客户端运行时会在相应重试节点到达前移除每个失败步骤的流式输出尾部;后续某次尝试成功后,该状态仍保持可见。未进入重试的终态失败会在其轮次边界渲染为持久的内联状态,展示适合显示的持久消息与可选错误码,但不会提供 Host 无法兑现的操作;AUTH 文案绝不会回显提供方给出的凭据片段。
|
||||
|
||||
@@ -40,7 +40,7 @@ Host 带 placement 的 `session/queue` 快照也会携带待处理 steering。Qu
|
||||
|
||||
`src/client/` 按领域组织。`contract/` 是 slot 声明、组合 props 与跨领域类型的共享表层;`skeleton/`、`chat/`、`input/`、`queue/` 和 `settings/` 保持内部实现,`apply.ts` 是它们的组装点。`/client` 导出表层只包含 loader entry、service class 和 contract 类型;组件与 store factory 经 slot 注册抵达页面。
|
||||
|
||||
完成的一轮以一个 turn-tail 空位收尾:chat 视图在收尾 assistant 正文与其 IconActions 之间渲染 `conversation.chat.turnTail` list slot,每轮一次、位于 `assistantActionsSeqs` 选出的 seq,派发 `TurnTailOwnerProps`(快照节点、收尾 seq,以及工具行的 `openFile`)。本包只拥有空位;填充它的产物行——从改写工具 `locations` 的派生、chip 上限、文案——都在 `@deepseek-ai/dsh-client-ui-deliverables` 里,因此把那个插件从 cordis.yml 中组合掉即可关闭该交互面,空位以零成本渲染为空。收尾正文经由同一个开关参与其中:chat 视图向可选的 `chatFileMentions` service(ctx.get;由同一插件提供)索取收尾消息的行内代码词表,并把结果接进 MarkdownText 的 `fileMentions` 约定——service 缺席时正文保持死文本。
|
||||
完成的一轮会物化一个有序的 `turn-tail` Conversation Node。它由引擎维护的 `TurnLocation` 提供收尾 Assistant 和 Turn data;renderer 在该 Node 的 IconActions 之前渲染 `conversation.chat.turnTail` chain,并派发包含 Turn、收尾 seq 和 `openFile` 的 `TurnTailOwnerProps`。本包只拥有空位;`@deepseek-ai/dsh-client-ui-deliverables` 把改写工具的 `locations` 累积到 Turn data,并拥有产物行、chip 上限和文案,因此把该插件从 cordis.yml 中组合掉即可关闭该交互面,空位以零成本渲染为空。收尾正文经由同一个开关参与其中:chat 视图向可选的 `chatFileMentions` service(ctx.get;由同一插件提供)索取收尾消息的行内代码词表,并把结果接进 MarkdownText 的 `fileMentions` seam——service 缺席时正文保持死文本。
|
||||
|
||||
## 模型体验
|
||||
|
||||
|
||||
@@ -56,11 +56,10 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
*/
|
||||
'conversation.chat.commandview': { kind: 'keyed'; scope: 'session'; owner: CommandRowOwnerProps }
|
||||
/**
|
||||
* The chat view's turn-tail chain: rendered between a closing assistant
|
||||
* message's body and its IconActions footer, once per turn (the render
|
||||
* site elects the closing seq). Entries derive a match from the owner
|
||||
* currency before mounting, so presentation components never mount only
|
||||
* to return null; an all-declined chain renders nothing.
|
||||
* The completed Turn Node's extension chain, rendered before that Node's
|
||||
* IconActions. Entries derive a match from the engine-owned Turn and
|
||||
* closing seq before mounting, so presentation components never mount
|
||||
* only to return null; an all-declined chain renders nothing.
|
||||
*/
|
||||
'conversation.chat.turnTail': { kind: 'chain'; scope: 'session'; owner: TurnTailOwnerProps }
|
||||
/** Selected Tool call output inside the details panel. */
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
emptyAssistantBlock, isAppendSurfaceEvent, isTokenDelta, toAssistantBlock, toAssistantBlocks,
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { AssistantChatData } from '../contract/chat-nodes.ts'
|
||||
import { chatNode } from './common.ts'
|
||||
import { CHAT_SYNTHETIC_SEQ_OFFSETS, chatNode } from './common.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
|
||||
interface ChatNodeDataMap {
|
||||
@@ -169,7 +169,7 @@ function finalNode(
|
||||
if (boundary === undefined || !hasInterruptionEvidence(blocks)) return undefined
|
||||
return {
|
||||
kind: 'assistant',
|
||||
seq: boundary.seq - 0.9,
|
||||
seq: boundary.seq + CHAT_SYNTHETIC_SEQ_OFFSETS.interruptedAssistant,
|
||||
time: boundary.time,
|
||||
turn: state.turn,
|
||||
step: state.step,
|
||||
|
||||
+4
-1
@@ -154,6 +154,9 @@ const EMPTY_CONTRIBUTION: LegacyContribution = {
|
||||
|
||||
function legacyContribution(raw: ChatConversationViewNode): LegacyContribution {
|
||||
const node = raw as ChatNode
|
||||
// Content-free settled Assistants remain in the finalized compatibility
|
||||
// stream so StatsLine preserves its pre-assembly step counts; hidden running
|
||||
// attempts have no final Node to contribute.
|
||||
if (raw.visibility !== 'visible' && node.kind !== 'assistant-step') return EMPTY_CONTRIBUTION
|
||||
switch (node.kind) {
|
||||
case 'user':
|
||||
@@ -221,7 +224,7 @@ function sameContribution(left: LegacyContribution | undefined, right: LegacyCon
|
||||
&& sameReferences(left.nodes, right.nodes)
|
||||
}
|
||||
|
||||
/** Incremental compatibility projection retained solely for unmigrated Trajectory consumers. */
|
||||
/** Incremental compatibility projection for StatsLine and legacy top-level snapshot fields. */
|
||||
class LegacySliceBuilder {
|
||||
private readonly contributions = new Map<string, LegacyContribution>()
|
||||
private readonly finalizedContributions = new Map<string, LegacyContribution>()
|
||||
|
||||
@@ -5,6 +5,16 @@ import type {
|
||||
ChatNode, ChatNodeDataMap, ChatNodeKind,
|
||||
} from '../contract/chat-nodes.ts'
|
||||
|
||||
/**
|
||||
* Relative positions in one durable event's seq neighborhood: interrupted
|
||||
* Assistant, its follow-up Nodes, then follow-ups to an ordinary final.
|
||||
*/
|
||||
export const CHAT_SYNTHETIC_SEQ_OFFSETS = {
|
||||
interruptedAssistant: -0.9,
|
||||
interruptedFollowup: -0.8,
|
||||
finalizedFollowup: 0.1,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Resolve one Context's best currently loaded event Location.
|
||||
* @param context - assembled business Context.
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { isAppendSurfaceEvent } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { ToolChatData } from '../contract/chat-nodes.ts'
|
||||
import { chatNode } from './common.ts'
|
||||
import { CHAT_SYNTHETIC_SEQ_OFFSETS, chatNode } from './common.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
|
||||
interface ChatNodeDataMap {
|
||||
@@ -190,7 +190,7 @@ function projectBlock(
|
||||
? sameReferences(block.subCalls, children) ? block : { ...block, subCalls: children }
|
||||
: {
|
||||
kind: 'tool-result',
|
||||
seq: interruptedAt.seq - 0.8,
|
||||
seq: interruptedAt.seq + CHAT_SYNTHETIC_SEQ_OFFSETS.interruptedFollowup,
|
||||
time: interruptedAt.time,
|
||||
callId: block.callId,
|
||||
call: { name: block.name, argsRaw: block.argsRaw },
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
AssistantChatData, FinalAssistantChatData, TurnTailChatData,
|
||||
} from '../contract/chat-nodes.ts'
|
||||
import { deriveTurnMetrics } from '../chat/turn-metrics.ts'
|
||||
import { chatNode } from './common.ts'
|
||||
import { CHAT_SYNTHETIC_SEQ_OFFSETS, chatNode } from './common.ts'
|
||||
|
||||
declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
|
||||
interface ChatNodeDataMap {
|
||||
@@ -85,7 +85,9 @@ function closingAnchor(context: ConversationNodeContext<TurnTailState>): number
|
||||
}
|
||||
if (event.type === 'assistant/message') {
|
||||
steps.set(coordinates.step, { streamedText: false, finalized: true })
|
||||
if (hasTextAssistant(event)) anchor = event.seq + 0.1
|
||||
if (hasTextAssistant(event)) {
|
||||
anchor = event.seq + CHAT_SYNTHETIC_SEQ_OFFSETS.finalizedFollowup
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ((event.type as string) === 'llm/retry') {
|
||||
@@ -93,7 +95,7 @@ function closingAnchor(context: ConversationNodeContext<TurnTailState>): number
|
||||
continue
|
||||
}
|
||||
if (event.type === 'step/end' && previous.streamedText && !previous.finalized) {
|
||||
anchor = event.seq - 0.8
|
||||
anchor = event.seq + CHAT_SYNTHETIC_SEQ_OFFSETS.interruptedFollowup
|
||||
}
|
||||
}
|
||||
return anchor
|
||||
|
||||
@@ -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 packages/client/ui-tool/README.md
|
||||
README.md: d6bc0f248cffaf4c65ecf6d97c7a4afb17fc8941
|
||||
README.zh.md: 6b6e2a153be6a3cad5b57379de6fdc0cd4a58ea4
|
||||
README.md: b33aef86a6ad161f70105968f308c5a587c25c24
|
||||
README.zh.md: 680a5149187bc43513c74134a9a58c4680af854c
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Client Tool presentation plugin. `ui-conversation` supplies one ordered root call through `conversation.chat.tool`; this package renders that root and its Code Dispatch children, then dispatches every atomic call through the keyed `tool.call.toolview` slot. Unregistered Tool names use the generic card.
|
||||
Client Tool presentation plugin. `ui-conversation` dispatches each ordered `tool-call` Conversation Node through the matching key of `conversation.chat.node`; this package renders its root and Code Dispatch children, then dispatches every atomic call through the keyed `tool.call.toolview` slot. Unregistered Tool names use the generic card.
|
||||
|
||||
Business UI packages register only their wire Tool names and atomic views. They do not pair Session events, rebuild the transcript, or own root/subcall topology. The Runtime remains authoritative for call/result pairing, lifecycle, and recursive `subCalls` projection; the conversation view remains authoritative for ChatFlow placement.
|
||||
|
||||
@@ -10,7 +10,7 @@ Business UI packages register only their wire Tool names and atomic views. They
|
||||
|
||||
`ToolCallTree` receives one root `ToolCallBlock` that already contains recursive `subCalls`, selection state, the session `cwd`, and Host callbacks for opening files and inspecting calls. It recursively walks the standard call blocks and sends the root and children at every depth through the same atomic dispatch path, without subscribing to a separate parent-to-children map.
|
||||
|
||||
Each root and child wrapper preserves the `conversation.chat.tool` call-anchor DOM contract used for paging and selection.
|
||||
Each root and child wrapper preserves the `data-chat-anchor-key="call:<id>"` and `data-chat-call-id` DOM contract used for paging and selection.
|
||||
|
||||
The package also fills `conversation.details.tool` with `ToolDetails`. The row and details renderers share the same pure card models for `terminal`, `read`, `diff`, `search`, and `web` render intents. Unknown intent tags and malformed wire card data fall back to flattened Tool result text.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
Client Tool 展示插件。`ui-conversation` 通过 `conversation.chat.tool` 交付一个已经排好位置的 root call;本包渲染该 root 及其 Code Dispatch 子调用,并把每个原子调用通过 keyed slot `tool.call.toolview` 分发。没有注册的 Tool 名称使用通用卡片。
|
||||
Client Tool 展示插件。`ui-conversation` 通过 `conversation.chat.node` 的同名 key 分发每个已排序的 `tool-call` Conversation Node;本包渲染其中的 root 及其 Code Dispatch 子调用,并把每个原子调用通过 keyed slot `tool.call.toolview` 分发。没有注册的 Tool 名称使用通用卡片。
|
||||
|
||||
业务 UI 包只注册 wire Tool 名称和原子视图,不配对 Session Event、不重建 transcript,也不拥有 root/subcall 拓扑。Runtime 继续负责 call/result 配对、生命周期和递归 `subCalls` 投影;conversation view 继续负责 ChatFlow 位置。
|
||||
|
||||
@@ -10,7 +10,7 @@ Client Tool 展示插件。`ui-conversation` 通过 `conversation.chat.tool` 交
|
||||
|
||||
`ToolCallTree` 接收一个已经包含递归 `subCalls` 的 root `ToolCallBlock`、selection 状态、会话 `cwd`,以及用于打开文件和检查调用的 Host 回调。它递归遍历标准 call block,让 root 与任意深度的 child 经过同一条原子分发路径,不再订阅独立的 parent-to-children map。
|
||||
|
||||
每个 root 和 child wrapper 都保留 `conversation.chat.tool` 的 call-anchor DOM 约定,供分页和 selection 使用。
|
||||
每个 root 和 child wrapper 都保留 `data-chat-anchor-key="call:<id>"` 与 `data-chat-call-id` DOM 约定,供分页和 selection 使用。
|
||||
|
||||
本包还通过 `ToolDetails` 填充 `conversation.details.tool`。行 renderer 与详情 renderer 为 `terminal`、`read`、`diff`、`search` 和 `web` render intent 共用同一组纯 card model。本版本不认识的 intent 标签和格式错误的 wire card 数据都会回退为压平的 Tool result 文本。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user