@deepseek-ai/dsh-client-ui-conversation 0.1.0-rc.8 → 0.1.1-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/README.zh.md +14 -14
- package/lib/client.js +143 -44
- package/lib/types/client/chat/StatsLine.d.ts +5 -3
- package/lib/types/client/contract/slots.d.ts +20 -1
- package/lib/types/client/conversation-nodes/turn-error.d.ts +6 -3
- package/lib/types/client/index.d.ts +1 -1
- package/package.json +49 -49
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 96f67677f690f12929cf691ab5901ded399504d3
|
|
6
|
+
README.zh.md: 05fdcd7a989fb74eb9f86f11cffb4eb77d38088a
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Conversation domain: skeleton (header/tabs/composer/empty state), chat view (gro
|
|
|
6
6
|
|
|
7
7
|
Compaction renders as one collapsed row at the checkpoint's flow position without replacing the transcript above it. Automatic compaction uses the context-compacted title. Every completed marker with a loaded `compaction/summary` event shows the replaced-item and estimated-token counts and discloses the summary on click. Manual `/compact` starts as a running `compact` row; on successful settlement its explicit summary-event reference folds that command into the checkpoint row under the same React key. A completed checkpoint keeps the context-compaction icon at rest and replaces it with the collapsed or expanded disclosure only on hover or keyboard focus. Input rejection, no compactable history, cancellation, and failure retain the generic command row and its handler-authored text. Pairing never depends on adjacency because durable context may be injected while compaction is running. The framed checkpoint payload is model-facing and never renders; when the cited `compaction/summary` event is outside the loaded window, the checkpoint remains visible but non-expandable.
|
|
8
8
|
|
|
9
|
-
The resident conversation shell survives no-session and session transitions. Without a current session it locks message actions and presents the whole dashed composer card as a trigger for the root-scoped `conversation.hero.workspace` Workspace picker; the textarea remains read-only and keyboard-accessible. The Hero's leading mark is the independent root-scoped `conversation.hero.brand.mark` slot, with the fish mark as its fallback. Selecting a Workspace connects or reuses its Host-owned blank session and opens that session without replacing the shell. The root always owns the same scrollport and Hero/composer subtree; separate strict-session header and body outlets fill their regions when the first Session arrives, so the Workspace picker, scroll body, composer seat, and textarea retain their React and DOM identity. Blank sessions render the same composer body as active sessions, while the InputHub carries drafts across Workspace switches and mirrors them into the session store. In the active phase the session header shows
|
|
9
|
+
The resident conversation shell survives no-session and session transitions. Without a current session it locks message actions and presents the whole dashed composer card as a trigger for the root-scoped `conversation.hero.workspace` Workspace picker; the textarea remains read-only and keyboard-accessible. The Hero's leading mark is the independent root-scoped `conversation.hero.brand.mark` slot, with the fish mark as its fallback. Selecting a Workspace connects or reuses its Host-owned blank session and opens that session without replacing the shell. The root always owns the same scrollport and Hero/composer subtree; separate strict-session header and body outlets fill their regions when the first Session arrives, so the Workspace picker, scroll body, composer seat, and textarea retain their React and DOM identity. Blank sessions render the same composer body as active sessions, while the InputHub carries drafts across Workspace switches and mirrors them into the session store. In the active phase the session header shows the current session title, optional lineage controls, and view tabs as ordinary column chrome; ordinary fork lineage remains session data and is not projected into the header. Beneath it the scrollport (`data-conversation-scroll`) holds the flowing views and the sticky composer stack (stats dock + input docks + bar). That scrollport reserves its scrollbar gutter unconditionally, and a view opting into a composer overlay leaves it a scroll container, so the input card keeps one horizontal position whether or not the transcript scrolls and whichever view tab is shown ([decision](../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). Wheel over the textarea chains: the capped draft scrolls locally until its edge, then forwards to that host. Safari alone receives a pre-paint recovery when a native edit shortens the draft and leaves stale soft-wrap overflow; draft growth, programmatic updates, and other browsers never read layout for that recovery ([decision](../../../.agents/notes/implemented/bug-fix/2026-08-13-safari-textarea-soft-wrap-reflow.md)).
|
|
10
10
|
|
|
11
11
|
Another plugin can make one session's composer inert through `ctx.conversation.blocks`: it sets a block carrying its own localized reason, and the bar renders the same disabled textarea with that reason as the placeholder — the no-workspace posture, reused. The push direction is the constraint, not a preference: the plugins that know a session cannot send (ui-model-selection, when no adapter serves its route) already depend on this package, so this package cannot read them. The model seat is the one control a block leaves live — every block this contract has is cleared by choosing a model, so locking it too would leave the composer asking for the only thing it prevents. A block is an affordance only; the Host refuses a prompt it cannot route regardless of what any client disables. The no-workspace state wins when both hold, because picking a workspace is the earlier prerequisite.
|
|
12
12
|
|
|
@@ -16,7 +16,7 @@ Chat business rows are independent registry contributions rather than a closed b
|
|
|
16
16
|
|
|
17
17
|
Approvals take over the composer through the chain this package declares: `ApprovalPanel` registers as a selector-routed `'conversation.composer'` entry (the ui-user-questions pattern) and occupies the composer in place of the InputBar while an approval wait is pending (amber strip, justification headline, paired command line from the running call's args, one-shot refuse/allow). The `PendingApproval` domain face in `contract/slots.ts` owns the wire encoding — the `ApprovalResponsePayload` value with the audit correlation — over the runtime's `PendingWait` carrier; the broadcast `approval/resolved` frame settles the wait and restores the composer. The runtime manager projects every approval or question wait through `SessionSummary.pendingInteraction`, including sessions never instantiated; `ui-workspace` owns its sidebar presentation. Pending waits leave the message flow entirely: questions (ui-user-questions) and approvals (ApprovalPanel) both answer through the composer takeover, so no display-only placeholder card remains. The composer's bottom-row Access seat mounts `PermissionSelect`, fed by the host-computed `permissions` projection through the standard-kit `useProjection` (key absence hides the chip); the chip opens a Menu-primitive dropdown whose kebab-case preset names render as title-case labels. Safe preset picks submit `/permission <preset>` immediately through the bar's injected `command` callback, while `danger-full-access` is presented as `Full access` and first opens an in-page Modal risk confirmation. The enabling action stays disabled until the user checks the acknowledgement; cancel, Escape, close, and mask click submit nothing.
|
|
18
18
|
|
|
19
|
-
The session header
|
|
19
|
+
The session header dispatches each current ordinary title and subagent breadcrumb through the optional session-scoped `'conversation.session.header.lineage'` seat, followed by the `'conversation.session.header.actions'` list and the independent `'conversation.session.header.utilities'` list at the right edge. Each lineage owner supplies plain breadcrumb identity and display text; the render site retains the ordinary title as fallback, and an ancestor also supplies its upward-navigation callback. Removing the occupant restores every title without affecting header actions, and optional Session utilities cannot reorder or move either group. The composer chain currency includes the current conversation `session`; ui-subagent selects one-shot or parent-unavailable addressed sessions for reason-specific read-only copy, while the ordinary InputBar keeps every addressed child Send-only because the continuation service exposes no public per-Activation cancellation operation and `session.cancel` would bypass its ownership.
|
|
20
20
|
|
|
21
21
|
Logged non-user messages render as a default-collapsed disclosure whose header names the role the runtime projected for the message — `上下文注入` for an injection, `跨会话召回` for a recalled session — followed by the producer name that projection read out of the durable source, so a reader distinguishes a skill catalog from a workspace instruction file or a recalled session without expanding. A direct message that cites another session precedes its recall row in durable order. The Chat snapshot associates exact labels only from that immediately following sourced recall, preserving multi-word titles without carrying one recall's labels onto a later direct message. Recall uses a chat-bubble glyph while other context keeps the document glyph; a source that names no producer shows the role alone. Composer and user-bubble references use the same inline language: a chat-bubble, file, or folder glyph plus business-color text, without a nested capsule. Like claimed slash commands, composer references keep their complete display text in the transparent textarea and use the aligned backdrop for color and the leading domain glyph; native text metrics own width, wrapping, selection, and caret placement. The occurrence range remains structured for serialization and boundary deletion, while an edit inside it converts the remaining characters to ordinary text. The session draft mirror stores each occurrence's clipboard projection, so a remount without the occurrence table restores canonical parseable reference text instead of a display-only label. The shared `DisclosureRow` primitive gives this context surface the same compact geometry as other flow rows while retaining context semantics: the expanded body follows its content height up to a 141px scrolling cap and synthesizes no tool state or summary ([historical disclosure decision](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.md), [producer-label decision](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md)). That body follows the form the producer declared on its durable source: `instructions` names the reconciled files above their text, `catalog` lists the entries the source recorded instead of the model-facing prose, and every other value — absent, unknown to this version, or carrying no usable fields — renders the opaque body, which shows the model-facing text with its real line breaks and the remaining source fields. The opaque body is the documented default, not a leftover: a resumed, forked, or foreign log must render whether or not its producer is mounted here. A durable or pending steering bubble shares the user bubble's presentation unadorned; its mid-turn position in the flow is the only steering signal the transcript shows.
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ A Think row stays collapsed by default and exposes live reasoning throughput wit
|
|
|
24
24
|
|
|
25
25
|
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. A path click through the injected `openFile` asks the Host to open that path (relative paths resolve against the session cwd). A Host or OS refusal opens an in-page dialog with the thrown reason and a Retry of the same path; Cancel, Escape, the close control, and a mask click dismiss it ([decision](../../../.agents/notes/implemented/bug-fix/2026-08-18-tool-row-file-open-failure.md)).
|
|
26
26
|
|
|
27
|
-
The chat flow projects
|
|
27
|
+
The chat flow projects each producer-correlated retry chain 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 attempt's streaming tail before its retry node arrives, while the status remains visible after a later attempt succeeds. A terminal failure renders as a persistent inline status at its turn boundary — beside the settled retry row when retries exhausted — 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.
|
|
28
28
|
|
|
29
29
|
`TodoDock` takes the `'conversation.input.dock'` list slot at `order: 0` — before Goal and Queue — and is the plan strip: it reads the host-computed `todos` projection via `useProjection` (standing plan: latest `todo/write` with no later `turn/start`) and renders `TodoPanel`, which takes the plain list, hides itself while the list is empty, and starts collapsed as a header of title plus its own `·`-joined per-status counts (localized, `1 completed · 2 in progress · 1 pending`, zero-count segments omitted). The dock adapter owns selection so the panel stays a pure function of its props. Anything the input-zone composer chain hides (a `conversation.composer` takeover such as ui-user-questions's) hides the whole dock, this strip included. The `todo_write` Tool row belongs to [`ui-tool`](../ui-tool/README.md).
|
|
30
30
|
|
|
@@ -40,7 +40,7 @@ Image intake accepts paste and whole-page drop: the bar binds document-level dra
|
|
|
40
40
|
|
|
41
41
|
The composer bar declares session-scoped single seats for `'conversation.input.plan'` (right of the local access-mode control) and `'conversation.input.model'` (immediately before the pending indicator and send/stop controls), plus list slots for overlay, dock, left, and right input extensions. Feature packages own each control and its state; ui-conversation supplies placement, the `locked` owner prop, and the standard slot shares. The leading plus button is a Command launcher, not an attachment surface: it asks the session's `InputTriggerController` to open only the `/` trigger's `command` source over the current textarea selection, while ui-input-trigger's existing `MenuView` remains the sole floating menu and pick path. No file row, file input, upload protocol, or second menu component is introduced. While the `plan` projection's effective target is plan mode, InputBar swaps its textarea placeholder to the plan-task wording, localized through the `conversation` locale namespace this package registers (the `placeholder.plan` / `hint.plan` keys) and shared verbatim with the claimed `/plan` command hint (a host-folded value read through the standard-kit `useProjection`; owner-supplied placeholders win). A pending composer takeover remains mounted when another conversation view is active so the blocked agent can still receive its answer; without a pending interaction, the active-session composer belongs to Chat. The composer-bar slot itself is `session-maybe`: with no current session the same bar keeps message actions inert (machine faces absent, `disabled` owner prop), while the whole dashed card opens the existing Workspace picker by pointer and the read-only textarea opens it through Enter or Space. Disabled controls release pointer events to the card, and the card contains `pointerdown` so the open picker's outside-close cannot race a reopen. The bar never swaps in a parallel tree, so the textarea DOM survives Workspace selection; strict-session control seats stay empty until a session exists.
|
|
42
42
|
|
|
43
|
-
The chat stats line takes its token accounting from the generic token-meter `tokenUsage` projection read through the standard-kit `useProjection`: billed input is uncached input plus cache reads and writes; cache hit divides cache reads by that total. The turn and step counts, the LLM and tool wall times, and the latency/throughput group all ride the whole-log `sessionStats` projection (host-folded from step boundaries, first-token chunks, tool pairs, and assembled messages), so paging and compaction cannot change any strip figure; an assembly without that unit falls back to the window fold over visible nodes, whose fields mirror the projection's. The strip averages each recorded step's TTFT and divides sampled output tokens by their summed decode spans into a latency/throughput group localized through the `conversation` locale namespace (`TTFT avg … · … tok/s` in English); a step missing a timing boundary or a usage sample drops out of those figures instead of skewing them, and durable count, token, and context groups remain visible when compaction leaves no assistant node in the loaded window. The turn-count, step-count, duration, cache, and token labels use the same namespace. Each settled turn additionally appends hover-revealed `TTFT {s}s · {tps} tok/s` labels to its assistant footer after the `Ran for` duration — the turn's first-step TTFT and its turn-aggregate decode throughput — gated on the turn's timing being in the loaded window (a contiguous log suffix, so an in-window turn carries every one of its steps) and omitting whichever figure is unrecorded. A deployment without token-meter drops the token groups; when the line overflows, it elides with an ellipsis and a delayed hover tooltip carries the full text only while actually clipped. Context occupancy renders as the composer's trailing ContextMeter: a 14px occupancy ring after the model seat, fed by `contextPressure` and rendered only once both a numerator and a route capacity are known, that click-opens a panel pairing the `percent used` header and `~used / capacity` figures with a color-segmented bar and `~`-prefixed heuristic composition rows (system prompt, tools, messages) from the `contextBreakdown` projection. The ring and header read `projectedTokens` — the provider sample carried forward over the surface's movement since — so a compaction registers immediately instead of after a further turn; the composition rows stay wholly heuristic and therefore still do not sum to the header ([rationale](../../llm/token-meter/README.md)). Occupancy is deliberately an approximation: numerator and capacity are independent last-wins projection fields, not one atomic request observation.
|
|
43
|
+
The chat stats line takes its token accounting from the generic token-meter `tokenUsage` projection read through the standard-kit `useProjection`: billed input is uncached input plus cache reads and writes; cache hit divides cache reads by that total. Every non-empty ratio starts with integer rounding. A non-full ratio adds decimal places only while the current precision would round to 100%, stopping at the minimum precision that remains below 100%; only a full cache hit displays 100%, and the precision has no fixed limit. The turn and step counts, the LLM and tool wall times, and the latency/throughput group all ride the whole-log `sessionStats` projection (host-folded from step boundaries, first-token chunks, tool pairs, and assembled messages), so paging and compaction cannot change any strip figure; an assembly without that unit falls back to the window fold over visible nodes, whose fields mirror the projection's. The strip averages each recorded step's TTFT and divides sampled output tokens by their summed decode spans into a latency/throughput group localized through the `conversation` locale namespace (`TTFT avg … · … tok/s` in English); a step missing a timing boundary or a usage sample drops out of those figures instead of skewing them, and durable count, token, and context groups remain visible when compaction leaves no assistant node in the loaded window. The turn-count, step-count, duration, cache, and token labels use the same namespace. Each settled turn additionally appends hover-revealed `TTFT {s}s · {tps} tok/s` labels to its assistant footer after the `Ran for` duration — the turn's first-step TTFT and its turn-aggregate decode throughput — gated on the turn's timing being in the loaded window (a contiguous log suffix, so an in-window turn carries every one of its steps) and omitting whichever figure is unrecorded. A deployment without token-meter drops the token groups; when the line overflows, it elides with an ellipsis and a delayed hover tooltip carries the full text only while actually clipped. Context occupancy renders as the composer's trailing ContextMeter: a 14px occupancy ring after the model seat, fed by `contextPressure` and rendered only once both a numerator and a route capacity are known, that click-opens a panel pairing the `percent used` header and `~used / capacity` figures with a color-segmented bar and `~`-prefixed heuristic composition rows (system prompt, tools, messages) from the `contextBreakdown` projection. The ring and header read `projectedTokens` — the provider sample carried forward over the surface's movement since — so a compaction registers immediately instead of after a further turn; the composition rows stay wholly heuristic and therefore still do not sum to the header ([rationale](../../llm/token-meter/README.md)). Occupancy is deliberately an approximation: numerator and capacity are independent last-wins projection fields, not one atomic request observation.
|
|
44
44
|
|
|
45
45
|
`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` exports contain only loader entries, service classes, and contract types; components and store factories reach the page through slot registrations.
|
|
46
46
|
|
package/README.zh.md
CHANGED
|
@@ -2,37 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 中文
|
|
4
4
|
|
|
5
|
-
会话领域:骨架(标题栏/标签页/编辑器/空状态)、聊天视图(分组步骤摘要流、流式尾部隔离与轮次状态)、编辑器 dock(与输入区一同 sticky 的会话统计行)、输入区 dock(队列行加 todo 计划条)、详情壳层,以及按 scope 寻址的 ConversationController。工具展示属于 [`ui-tool`](../ui-tool/README.md)。
|
|
5
|
+
会话领域:骨架(标题栏/标签页/编辑器/空状态)、聊天视图(分组步骤摘要流、流式尾部隔离与轮次状态)、编辑器 dock(与输入区一同 sticky 的会话统计行)、输入区 dock(队列行加 todo 计划条)、详情壳层,以及按 scope 寻址的 ConversationController。工具展示属于 [`ui-tool`](../ui-tool/README.zh.md)。
|
|
6
6
|
|
|
7
7
|
压缩(compaction)在检查点自身的消息流位置渲染为一行折叠标记,不替换其上方的 transcript(文本记录)。自动压缩使用「上下文已压缩」标题。每个已加载对应 `compaction/summary` 事件的完成标记都会显示被替换条目数量和估算 token 数量,并可点击展开摘要。手动 `/compact` 开始时显示为运行中的 `compact` 行;成功结算后,其显式摘要事件引用会在保持同一 React key 的前提下把该命令折叠进检查点行。完成的检查点静止时保留上下文压缩(context compaction)图标,仅在悬停或键盘聚焦时将其替换为收起/展开指示图标。输入被拒绝、没有可压缩历史、取消和失败时仍使用通用命令行及处理器撰写的文本。配对绝不依赖相邻关系,因为压缩运行期间可能注入持久上下文。面向模型的带框检查点载荷绝不渲染;被引用的 `compaction/summary` 事件位于已加载窗口之外时,检查点仍然可见但不可展开。
|
|
8
8
|
|
|
9
|
-
常驻会话壳会跨无会话与会话状态切换而保留。没有当前会话时,它会锁定消息操作,并让整张虚线编辑器卡片成为根作用域 `conversation.hero.workspace` Workspace picker 的入口;textarea 保持只读且支持键盘操作。Hero 前方的标记是独立的根作用域 `conversation.hero.brand.mark` slot,未被占用时回退到鱼形标记。选择 Workspace 会连接或复用由 Host 拥有的空白会话,并在不替换会话壳的情况下打开该会话。根组件始终拥有同一个滚动容器与 Hero/编辑器子树;首个会话到达时,彼此独立的严格会话页头和主体 outlet 只填入各自区域,因此 Workspace picker、滚动主体、编辑器 seat 与 textarea 都保留原有 React 和 DOM identity。空白会话与活跃会话渲染相同的输入区主体;InputHub 则在 Workspace 切换间携带草稿,并将草稿镜像到会话 store。活跃阶段,会话标题栏作为普通列 chrome
|
|
9
|
+
常驻会话壳会跨无会话与会话状态切换而保留。没有当前会话时,它会锁定消息操作,并让整张虚线编辑器卡片成为根作用域 `conversation.hero.workspace` Workspace picker 的入口;textarea 保持只读且支持键盘操作。Hero 前方的标记是独立的根作用域 `conversation.hero.brand.mark` slot,未被占用时回退到鱼形标记。选择 Workspace 会连接或复用由 Host 拥有的空白会话,并在不替换会话壳的情况下打开该会话。根组件始终拥有同一个滚动容器与 Hero/编辑器子树;首个会话到达时,彼此独立的严格会话页头和主体 outlet 只填入各自区域,因此 Workspace picker、滚动主体、编辑器 seat 与 textarea 都保留原有 React 和 DOM identity。空白会话与活跃会话渲染相同的输入区主体;InputHub 则在 Workspace 切换间携带草稿,并将草稿镜像到会话 store。活跃阶段,会话标题栏作为普通列 chrome,显示当前会话 title、可选谱系控件和视图标签;普通 fork 谱系仍保留为会话数据,不投影到标题栏。其下滚动容器(`data-conversation-scroll`)承载流动排版的各视图与 sticky 编辑器栈(统计 dock+输入区 dock+输入栏)。该滚动容器无条件预留自己的滚动条槽,选用编辑器 overlay 的视图也仍把它保留为滚动容器,因此无论对话记录是否滚动、无论展示哪个视图标签,输入卡片都保持同一个横向位置([决策](../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.zh.md))。textarea 上的滚轮会链式处理:限高草稿先在本地滚动,到达边缘后再转交给该宿主。只有 Safari 会在原生编辑缩短草稿并留下陈旧软换行溢出时执行绘制前恢复;草稿增长、程序化更新与其他浏览器都不会为这项恢复读取布局([决策](../../../.agents/notes/implemented/bug-fix/2026-08-13-safari-textarea-soft-wrap-reflow.zh.md))。
|
|
10
10
|
|
|
11
11
|
别的插件可以经 `ctx.conversation.blocks` 让某个会话的编辑器变为惰性:它设置一个携带自己本地化理由的 block,输入栏就渲染同一个禁用的 textarea,并把该理由作为 placeholder——复用无 Workspace 时的那套姿态。推送方向是约束而非偏好:知道某会话发不出消息的插件(ui-model-selection,在没有适配器服务其路由时)本就依赖本包,因此本包读不到它们。模型 seat 是 block 唯一保留可用的控件——这份约定里的每个 block 都靠选模型来解除,把它一起锁上会让编辑器索要它自己拦下的那件事。block 只是提示性设计;无论客户端禁用了什么,宿主都会拒绝一个它无法路由的提示词。两者同时成立时以无 Workspace 姿态为准,因为选 Workspace 是更靠前的前提。
|
|
12
12
|
|
|
13
13
|
视图环是一个 slot:严格会话主体注册在 `children` 表中声明会话作用域的 `'conversation.view'` 列表,并通过自身的 renderSlot share 渲染活跃配置项(`only: <active id>`);视图标签页则从注册选项(`id`/`order`/`label`)投影而来。聊天视图是该包自身的配置项;ui-trajectory 等插件通过 `ctx.slots.register` 贡献标签页,每个视图负责自己的 chrome。
|
|
14
14
|
|
|
15
|
-
Chat 业务行是彼此独立的注册表贡献,不是封闭的内建联合。Client 插件通过 declaration merging 增加类型化 `ChatNodeDataMap` key,在 `ctx.conversationEvents` 上注册 `ConversationNodeDefinition`,再向 `conversation.chat.node` 注册匹配的 keyed renderer;它无须修改会话 fold 或中央 renderer switch。稳定事件 id、append/prepend 回放、Location data 与 renderer 约束见 [Conversation Node 实操手册](../../../docs/cookbook/adding-a-conversation-node.md)。
|
|
15
|
+
Chat 业务行是彼此独立的注册表贡献,不是封闭的内建联合。Client 插件通过 declaration merging 增加类型化 `ChatNodeDataMap` key,在 `ctx.conversationEvents` 上注册 `ConversationNodeDefinition`,再向 `conversation.chat.node` 注册匹配的 keyed renderer;它无须修改会话 fold 或中央 renderer switch。稳定事件 id、append/prepend 回放、Location data 与 renderer 约束见 [Conversation Node 实操手册](../../../docs/cookbook/adding-a-conversation-node.zh.md)。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
会话页头通过可选的会话作用域 `'conversation.session.header.lineage'` seat 派发当前普通 title 与每一级 subagent 面包屑,随后依次渲染 `'conversation.session.header.actions'` 列表和最右侧独立的 `'conversation.session.header.utilities'` 列表。每个谱系 owner 都会提供纯数据形式的面包屑身份与显示文本;render site 保留普通 title 作为回退,祖先还会提供向上导航的回调。移除 occupant 会恢复每个 title,且不影响页头操作;可选的会话工具不会改变这两个区域的顺序或位置。编辑器链的 currency 包含当前对话 `session`;ui-subagent 会选取 one-shot 或 parent 不可用的已寻址会话,并按原因显示只读文案,而普通 InputBar 会让所有已寻址 child 仅保留 Send,因为继续执行服务不公开逐 Activation 取消操作,`session.cancel` 也会绕过其所有权。
|
|
18
18
|
|
|
19
|
-
已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 `上下文注入`,召回为 `跨会话召回`——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。引用其他会话的直接消息在持久顺序中位于其召回行之前。Chat 快照只从紧随其后的带来源召回中关联准确标签,因此既能保留多词标题,也不会把一条召回的标签带到后续直接消息上。召回使用聊天气泡图标,其他上下文保留文档图标;来源未提供生产者名称时只显示角色。输入框与用户气泡中的引用使用同一种行内语言:聊天气泡、文件或文件夹图标加业务色文字,不嵌套胶囊容器。与已认领的 slash command 相同,输入框引用会把完整展示文本保留在透明 textarea 中,再用对齐的 backdrop 提供颜色和开头的领域图标;宽度、换行、选择区与光标位置均由原生文本度量决定。occurrence 范围仍为序列化与边界整段删除保留结构身份,在范围内部编辑则会把剩余字符转为普通文本。会话草稿镜像会存储每个 occurrence 的剪贴板投影,因此在 occurrence 表缺失的情况下重新挂载时,会恢复可解析的规范引用文本,而不是仅供显示的标签。共享的 `DisclosureRow` 原子组件让该上下文界面与消息流中的其他紧凑行保持相同几何,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态或摘要([历史展开项决策](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.md)、[生产者标签决策](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.md))。该内容区按生产方在持久来源上声明的形态渲染:`instructions` 在正文之上列出它对账过的文件,`catalog` 列出来源记录的条目而非面向模型的正文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源字段列出。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡沿用用户气泡的呈现,不加任何装饰;transcript 中唯一的 steering 信号是它出现在轮次中途的位置。
|
|
19
|
+
已记录的非用户消息渲染为默认折叠的展开项,标题栏先给出运行时为该消息投影出的角色——注入为 `上下文注入`,召回为 `跨会话召回`——其后是该投影从持久来源读出的生产者名称,因此读者无需展开即可区分 skill(技能)目录、工作区指令文件与被召回的会话。引用其他会话的直接消息在持久顺序中位于其召回行之前。Chat 快照只从紧随其后的带来源召回中关联准确标签,因此既能保留多词标题,也不会把一条召回的标签带到后续直接消息上。召回使用聊天气泡图标,其他上下文保留文档图标;来源未提供生产者名称时只显示角色。输入框与用户气泡中的引用使用同一种行内语言:聊天气泡、文件或文件夹图标加业务色文字,不嵌套胶囊容器。与已认领的 slash command 相同,输入框引用会把完整展示文本保留在透明 textarea 中,再用对齐的 backdrop 提供颜色和开头的领域图标;宽度、换行、选择区与光标位置均由原生文本度量决定。occurrence 范围仍为序列化与边界整段删除保留结构身份,在范围内部编辑则会把剩余字符转为普通文本。会话草稿镜像会存储每个 occurrence 的剪贴板投影,因此在 occurrence 表缺失的情况下重新挂载时,会恢复可解析的规范引用文本,而不是仅供显示的标签。共享的 `DisclosureRow` 原子组件让该上下文界面与消息流中的其他紧凑行保持相同几何,同时保留上下文语义:展开内容区的高度会随内容自适应,最大为 141px,超出后滚动,且不会合成工具状态或摘要([历史展开项决策](../../../.agents/notes/archived/feature/2026-07-30-web-context-injection-disclosure.md)、[生产者标签决策](../../../.agents/notes/implemented/feature/2026-08-04-web-context-source-and-steer-marks.zh.md))。该内容区按生产方在持久来源上声明的形态渲染:`instructions` 在正文之上列出它对账过的文件,`catalog` 列出来源记录的条目而非面向模型的正文,其余取值——未声明、本版本不认识、或字段不可用——一律渲染 opaque 内容区,即按真实换行展示面向模型的文本,并把剩余来源字段列出。opaque 不是兜底剩余物而是有文档的默认:恢复的、fork 的、外部写入的日志,无论其生产方是否挂载在此处,都必须渲染得出来。持久或待处理的 steering(中途引导)气泡沿用用户气泡的呈现,不加任何装饰;transcript 中唯一的 steering 信号是它出现在轮次中途的位置。
|
|
20
20
|
|
|
21
|
-
Think 行默认保持折叠,并在不展开思维链的情况下暴露实时推理(reasoning)吞吐:当推理块是流式输出尾部时,摘要从结算后的首行切换到最新的非空行,其单行滚动区会随每个 delta 追到行内末端。展开该行会移除移动摘要,让完整推理进入普通页面流,因此页面阅读不会与内部跟随器争夺滚动;结算后恢复左对齐的稳定首行摘要([决策](../../../.agents/notes/implemented/feature/2026-08-02-web-thinking-tail-scroll.md))。
|
|
21
|
+
Think 行默认保持折叠,并在不展开思维链的情况下暴露实时推理(reasoning)吞吐:当推理块是流式输出尾部时,摘要从结算后的首行切换到最新的非空行,其单行滚动区会随每个 delta 追到行内末端。展开该行会移除移动摘要,让完整推理进入普通页面流,因此页面阅读不会与内部跟随器争夺滚动;结算后恢复左对齐的稳定首行摘要([决策](../../../.agents/notes/implemented/feature/2026-08-02-web-thinking-tail-scroll.zh.md))。
|
|
22
22
|
|
|
23
|
-
聊天视图保留工具的消息流位置,但委托其展示。每个已排序的 `tool-call` Conversation Node 都通过 `conversation.chat.node` 的同名 key 分发;详情壳层则通过 `conversation.details.tool` 传递当前选中的调用。组装后的 Web bundle 为该 Chat Node key 注册 [`ui-tool`](../ui-tool/README.md),由后者渲染运行时已投影的递归 root/child 树,并负责按名称分发、通用展示和 render-intent 卡片;只有详情席位会在该 renderer 缺席时保留 raw-result fallback。经注入的 `openFile` 点击路径会请 Host 打开该路径(相对路径按会话 cwd 解析)。Host 或操作系统拒绝时,页面内对话框展示抛出的原因,并提供对同一路径的重试;取消、Escape、关闭控件和点击遮罩会关掉对话框([决策](../../../.agents/notes/implemented/bug-fix/2026-08-18-tool-row-file-open-failure.md))。
|
|
23
|
+
聊天视图保留工具的消息流位置,但委托其展示。每个已排序的 `tool-call` Conversation Node 都通过 `conversation.chat.node` 的同名 key 分发;详情壳层则通过 `conversation.details.tool` 传递当前选中的调用。组装后的 Web bundle 为该 Chat Node key 注册 [`ui-tool`](../ui-tool/README.zh.md),由后者渲染运行时已投影的递归 root/child 树,并负责按名称分发、通用展示和 render-intent 卡片;只有详情席位会在该 renderer 缺席时保留 raw-result fallback。经注入的 `openFile` 点击路径会请 Host 打开该路径(相对路径按会话 cwd 解析)。Host 或操作系统拒绝时,页面内对话框展示抛出的原因,并提供对同一路径的重试;取消、Escape、关闭控件和点击遮罩会关掉对话框([决策](../../../.agents/notes/implemented/bug-fix/2026-08-18-tool-row-file-open-failure.zh.md))。
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
聊天流会把每条生产方关联的重试链投影为一个稳定的弱化状态行,并用最新一次尝试更新该行;每个重试事件仍保留在运行时快照与会话日志中。前端倒计时以客户端收到事件的时刻为计划延迟的起点,避免 Host 与浏览器的时钟偏差;剩余时间向上取整到秒,且下限为 1 秒。最近一次尚未完成的重试会显示从左到右的文字渐变动画。后续轮次事实用于区分已开始的尝试与在退避期间取消的尝试,Host 的 running 位只控制实时动画;随后该行会显示静态的已完成或已取消标签。normal 策略行显示有限重试上限;always 策略行显示 `∞`。激活该行会显示最近一次重试的精确延迟和失败消息。客户端运行时会在相应重试节点到达前移除每次失败尝试的流式输出尾部;后续某次尝试成功后,该状态仍保持可见。终态失败会在其轮次边界渲染为持久的内联状态——重试耗尽后与定格的重试行并列——展示适合显示的持久消息与可选错误码,但不会提供 Host 无法兑现的操作;AUTH 文案绝不会回显提供方给出的凭据片段。
|
|
26
26
|
|
|
27
27
|
审批通过本包声明的链条接管编辑器:`ApprovalPanel` 注册为按选择器路由的 `'conversation.composer'` 配置项(ui-user-questions 模式),在审批等待未决期间取代 InputBar 占据编辑器(琥珀色条、理由标题、来自运行中调用参数的配对命令行、一次性的拒绝/允许)。`contract/slots.ts` 中的 `PendingApproval` 领域面在运行时 `PendingWait` 载体之上拥有 wire 编码——带审计关联的 `ApprovalResponsePayload` 值;广播的 `approval/resolved` 帧使等待落定并恢复编辑器。运行时 manager 会将所有审批或问题等待通过 `SessionSummary.pendingInteraction` 投影出来,未实例化的会话也不例外;`ui-workspace` 负责其侧边栏呈现。未决等待完全离开消息流:问题(ui-user-questions)与审批(ApprovalPanel)都经编辑器接管作答,不再保留只读占位卡。编辑器底行的 Access 席位挂载 `PermissionSelect`,由 host 计算的 `permissions` 投影经标准工具包 `useProjection` 供数(key 缺席即隐藏 chip);chip 打开 Menu 原语下拉,其中 kebab-case 预设名渲染为 Title Case 标签;普通安全预设会立即经输入栏注入的 `command` 回调提交 `/permission <preset>`,而 `danger-full-access` 在界面中显示为 `Full access`,选择后先打开页面内的 Modal 风险确认。用户勾选确认项前启用按钮始终不可用;取消、Escape、关闭按钮与点击遮罩都不会提交命令。
|
|
28
28
|
|
|
29
|
-
`TodoDock` 以 `order: 0` 占用 `'conversation.input.dock'` 列表 slot(位于 Goal 与 Queue 之前),作为计划条读取 host 计算的 `todos` 投影(当前计划:其后没有更晚 `turn/start` 的最近一次 `todo/write`)并渲染 `TodoPanel`。面板接收纯列表,列表为空时自我隐藏;列表非空时默认折叠,表头显示标题及以 `·` 连接的各状态计数(如 `1 已完成 · 2 进行中 · 1 待处理`,省略零计数)。dock adapter 拥有 selection,因此面板保持为 props 的纯函数。输入区 composer 链隐藏的一切也会隐藏整个 dock。`todo_write` 工具行属于 [`ui-tool`](../ui-tool/README.md)。
|
|
29
|
+
`TodoDock` 以 `order: 0` 占用 `'conversation.input.dock'` 列表 slot(位于 Goal 与 Queue 之前),作为计划条读取 host 计算的 `todos` 投影(当前计划:其后没有更晚 `turn/start` 的最近一次 `todo/write`)并渲染 `TodoPanel`。面板接收纯列表,列表为空时自我隐藏;列表非空时默认折叠,表头显示标题及以 `·` 连接的各状态计数(如 `1 已完成 · 2 进行中 · 1 待处理`,省略零计数)。dock adapter 拥有 selection,因此面板保持为 props 的纯函数。输入区 composer 链隐藏的一切也会隐藏整个 dock。`todo_write` 工具行属于 [`ui-tool`](../ui-tool/README.zh.md)。
|
|
30
30
|
|
|
31
31
|
`QueueDock` 是 `order: 20` 的末端 input-dock 条目。队列为空时隐藏;只有一个待处理项时直接渲染该行;存在两个或更多待处理项时,默认收起为 `"<n> 条排队消息"` 表头,其按钮可展开或收起完整列表。表头暴露 `aria-expanded` 和 `aria-controls`;展开后的列表以 180px 为高度上限,并可滚动。存在进行中的编辑或变更时,列表行会保持可见;队列清空后,下一次出现队列时会恢复默认收起状态。普通会话中的每条可见行仍是单行预览,并提供针对精确单次入队项的编辑、删除和严格 steering 操作;已寻址 subagent 则保留只读行,因为其继续执行传输不提供 Queue 变更。如果严格 steering 输给已关闭的窗口,原单次入队项会留在 Queue 中正常投递;如果驱动器已经认领该项,正常投递就已开始。这两种已收敛的竞态都不显示失败,传输和未知错误仍会显示。
|
|
32
32
|
|
|
33
|
-
Host 带 placement 的 `session/queue` 快照也会携带待处理 steering。QueueDock 会将其过滤掉,ChatView 则把它投影为会话流末尾带复制操作的用户样式气泡;非用户来源的 next-step 项(注入上下文)改以 `context` placement 广播,领取前不在任何界面渲染。与所有用户样式气泡一样,这里不显示 fork。Host 会等携带该 steering 的持久 `user/message` 进入 mux 流之后再退役 steering。客户端运行时接纳该实时事件时,会在发布快照前退役第一个匹配的当前 steering 单次入队项;历史事件无法隐藏后来复用同一 `MessageId` 的单次入队项。气泡交接时因而不会产生空档或重复,会立即从持久节点恢复复制操作与时钟——steering 气泡与 user 气泡一样不带分支操作([决策](../../../.agents/notes/implemented/simplification/2026-08-06-user-bubbles-drop-the-branch-action.md))——并能在重连后从同一权威恢复。
|
|
33
|
+
Host 带 placement 的 `session/queue` 快照也会携带待处理 steering。QueueDock 会将其过滤掉,ChatView 则把它投影为会话流末尾带复制操作的用户样式气泡;非用户来源的 next-step 项(注入上下文)改以 `context` placement 广播,领取前不在任何界面渲染。与所有用户样式气泡一样,这里不显示 fork。Host 会等携带该 steering 的持久 `user/message` 进入 mux 流之后再退役 steering。客户端运行时接纳该实时事件时,会在发布快照前退役第一个匹配的当前 steering 单次入队项;历史事件无法隐藏后来复用同一 `MessageId` 的单次入队项。气泡交接时因而不会产生空档或重复,会立即从持久节点恢复复制操作与时钟——steering 气泡与 user 气泡一样不带分支操作([决策](../../../.agents/notes/implemented/simplification/2026-08-06-user-bubbles-drop-the-branch-action.zh.md))——并能在重连后从同一权威恢复。
|
|
34
34
|
|
|
35
|
-
键盘消息提交会根据所寻址会话的运行状态和 steering 能力解析投递方式。空闲时,Enter 和 Cmd/Ctrl+Enter 都执行普通 Queue 发送。主会话运行期间,由 Host settings 支撑的 `ui-conversation.busyEnter` General Settings 偏好会把普通 Enter 分配为 `Queue`(默认值)或 `Steer`,Cmd/Ctrl+Enter 则执行另一种行为;本地 settings 提供方将其存入 `$DSH_HOME/settings.yaml`,因此该选择会跟随同一个用户 home 跨越 Web 端口。Shift+Enter 仍然换行。草稿为空时,Cmd/Ctrl+Enter 改为按 FIFO 顺序把仍在排队的消息全部插话进运行中的轮次(把 dock 的逐条严格 steer 操作应用于整个队列);空草稿 + 普通 Enter 仍是无操作。这个整队列手势可用时,文本框 placeholder 会提示该手势;owner 提供的 placeholder 仍然优先。已寻址 subagent 即使正在运行,也会让这两个手势都使用其仅支持 Queue 的继续执行传输。该偏好只影响支持 steering 的繁忙态手势对,发送按钮与非键盘提交操作仍使用 Queue。Composer Steer 复用现有尽力而为的 `session.prompt(mode: 'steer')` 约定:如果当前 next-step 窗口在接纳前关闭,AgentLoop 会把消息接纳为下一条唤醒 Queue 轮次,不显示失败,也不会丢失草稿事务。该持久化边界由[Host settings 支撑的偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md)拥有。
|
|
35
|
+
键盘消息提交会根据所寻址会话的运行状态和 steering 能力解析投递方式。空闲时,Enter 和 Cmd/Ctrl+Enter 都执行普通 Queue 发送。主会话运行期间,由 Host settings 支撑的 `ui-conversation.busyEnter` General Settings 偏好会把普通 Enter 分配为 `Queue`(默认值)或 `Steer`,Cmd/Ctrl+Enter 则执行另一种行为;本地 settings 提供方将其存入 `$DSH_HOME/settings.yaml`,因此该选择会跟随同一个用户 home 跨越 Web 端口。Shift+Enter 仍然换行。草稿为空时,Cmd/Ctrl+Enter 改为按 FIFO 顺序把仍在排队的消息全部插话进运行中的轮次(把 dock 的逐条严格 steer 操作应用于整个队列);空草稿 + 普通 Enter 仍是无操作。这个整队列手势可用时,文本框 placeholder 会提示该手势;owner 提供的 placeholder 仍然优先。已寻址 subagent 即使正在运行,也会让这两个手势都使用其仅支持 Queue 的继续执行传输。该偏好只影响支持 steering 的繁忙态手势对,发送按钮与非键盘提交操作仍使用 Queue。Composer Steer 复用现有尽力而为的 `session.prompt(mode: 'steer')` 约定:如果当前 next-step 窗口在接纳前关闭,AgentLoop 会把消息接纳为下一条唤醒 Queue 轮次,不显示失败,也不会丢失草稿事务。该持久化边界由[Host settings 支撑的偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md)拥有。
|
|
36
36
|
|
|
37
37
|
逐会话 UI 状态中的选择与活跃视图位于已声明的聊天 store(`stores.ts` `createChatStore`)中;InputHub 拥有输入区状态机,并将草稿镜像到该 store 以便持久化。apply 将同一个 store handle 传给严格限定于会话的子树、聊天视图和详情注册,因此每个会话内共享一个实例,框架拥有其生命周期。组件保持纯粹:框架标准工具包提供 `useSession`/`sessionId`、全局 `useSessions`/`useWorkspaces`,以及输入状态机的 `useInput`/`inputActions`;store 表层与 inject factory 提供其余状态和回调。
|
|
38
38
|
|
|
@@ -40,7 +40,7 @@ Host 带 placement 的 `session/queue` 快照也会携带待处理 steering。Qu
|
|
|
40
40
|
|
|
41
41
|
输入栏为 `'conversation.input.plan'`(位于本地 access 模式控件右侧)和 `'conversation.input.model'`(渲染在 pending 指示器与发送/停止控件之前)声明会话作用域的单实例 seat,并为 overlay、dock、left 和 right 输入扩展声明列表 slot。各功能包拥有相应控件及其状态;ui-conversation 提供放置位置、`locked` owner prop 和标准 slot share。前置加号按钮是 Command launcher,而非附件入口:它要求当前会话的 `InputTriggerController` 基于 textarea 当前 selection,只打开 `/` trigger 的 `command` source,同时 ui-input-trigger 既有的 `MenuView` 仍是唯一的浮层菜单与 pick 路径。不引入 File 行、file input、上传协议或第二套菜单组件。当 `plan` 投影的有效目标为 plan mode 时,InputBar 将文本框 placeholder 切换为 plan 任务措辞,经本包注册的 `conversation` locale 命名空间(`placeholder.plan` / `hint.plan` 键)本地化,并与已认领 `/plan` 命令的提示逐字共用同一份文案(经标准套件 `useProjection` 读取的 host 折叠值;owner 提供的 placeholder 优先)。另一个会话视图活跃时,待处理的 composer 接管仍保持挂载,使被阻塞的 agent(智能体)仍能收到回答;没有待处理交互时,活跃会话的 composer 归 Chat 所有。composer bar slot 本身为 `session-maybe`:没有当前会话时,同一个 bar 会让消息操作保持不可交互(machine face 均缺席、`disabled` owner prop),整张虚线卡片可经指针打开现有 Workspace picker,只读 textarea 也可通过 Enter 或 Space 打开。禁用控件会把指针事件交给卡片,卡片也会拦下 `pointerdown`,避免已打开 picker 的外点关闭与重新打开发生竞态。它不会换入一棵平行树,因此选择 Workspace 时 textarea DOM 不会被销毁;严格会话作用域的控件 seat 在会话存在之前保持为空。
|
|
42
42
|
|
|
43
|
-
聊天统计行的 token 账目来自经标准套件 `useProjection` 读取的通用 token-meter 投影 `tokenUsage
|
|
43
|
+
聊天统计行的 token 账目来自经标准套件 `useProjection` 读取的通用 token-meter 投影 `tokenUsage`:计费输入为未缓存输入、缓存读取与缓存写入之和;缓存命中率以缓存读取除以该总量。所有非空比率都先按整数舍入。非满命中只有在当前精度会舍入成 100% 时才增加小数位,并在首次得到低于 100% 的结果时停止;只有完整缓存命中才显示 100%,且精度没有固定上限。轮次与步骤计数、LLM(大语言模型)与工具墙钟时间、以及延迟/吞吐分组都来自全日志的 `sessionStats` 投影(Host 端从步边界、首 token chunk、工具配对与已组装消息折算),因此分页与压缩都无法改变统计条的任何数字;未组合该单元的装配回退为对可见节点做窗口折算,其字段与投影一一对应。统计条把每个有完整记录的步骤的 TTFT(首 token 延迟)取平均,并用采样到的输出 token 数除以其解码时长之和,得到经 `conversation` locale 命名空间本地化的延迟/吞吐分组(中文为 `首 token 平均 … · … tok/s`);缺少某个 timing 边界或 usage 采样的步骤会直接退出这些数字,而不是让它们失真;压缩(compaction)使已加载窗口不再包含 assistant 节点时,持久计数、token 与上下文分组仍保持可见。轮次计数、步骤计数、耗时、缓存与 token 各项的标签也使用同一命名空间。每个已结算轮次还会在其 assistant footer 的 `用时` 之后追加 hover 才显示的 `首 token {s}秒 · {tps} tok/s` 标签——即该轮次首个步骤的 TTFT 与轮次聚合的解码吞吐——仅当该轮次的 timing 位于已加载窗口内才显示(窗口是日志的连续后缀,因此窗口内的轮次必然带着它的全部步骤),未记录的数字会各自省略。未组合 token-meter 的部署会整组省略 token 分组;统计行过长时以省略号截断,仅在内容真的被裁切时由延迟 hover tooltip 承载完整文本。上下文占用率渲染为 composer 尾部的 ContextMeter:模型座位之后的一枚 14px 占用圆环,由 `contextPressure` 供数,仅当分子与路由容量都已知时才渲染;点击弹出的面板把「已用百分比」标题与 `~已用 / 容量` 数字,与来自 `contextBreakdown` 投影、带 `~` 前缀的启发式组成明细行(系统提示词、工具、对话消息)及分色分段进度条并列。圆环与标题读取 `projectedTokens`——把提供方样本沿此后表层的增减推进到当下——因此压缩会立刻反映出来,而不必再等一整轮;组成明细行仍是纯启发式,因此加起来依然不等于标题数字([原理](../../llm/token-meter/README.zh.md))。占用率是刻意为之的近似值:分子与容量是两个相互独立的「后写覆盖」投影字段,并非同一次请求的原子观测。
|
|
44
44
|
|
|
45
45
|
`src/client/` 按领域组织。`contract/` 是 slot 声明、组合 props 与跨领域类型的共享表层;`skeleton/`、`chat/`、`input/`、`queue/` 和 `settings/` 保持内部实现,`apply.ts` 是它们的组装点。`/client` 导出表层只包含 loader entry、service class 和 contract 类型;组件与 store factory 经 slot 注册抵达页面。
|
|
46
46
|
|
|
@@ -58,8 +58,8 @@ Host 带 placement 的 `session/queue` 快照也会携带待处理 steering。Qu
|
|
|
58
58
|
|
|
59
59
|
- **统计行的回退折算只覆盖窗口内消息流**:未组合 `sessionStats` 投影单元的装配中,所有数字由快照的 assistant `timing` 与工具 call/result 配对折算,落在已加载事件窗口之外的节点(更早的历史)不计入,数字随加载页数增长。
|
|
60
60
|
- **详情面板没有入口**:`ChatViewInjected.openDetails` 虽已实现却无人调用,因此以原始形式显示已选择调用的那部分在组装后的应用中不可达。没有 Input/Output/Metadata 切换、Prev/Next 步进,也没有 trajectory 深链接。
|
|
61
|
-
- **assistant 逐消息分页是预留 slot**:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/时钟/分支)只挂在每个已结束轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述、纯 Think 节点,以及仍在产出步骤的轮次里的所有节点都不带 chrome。除非该消息同时也是已完成轮次的最后一个 transcript 节点,否则分支保持禁用;启用后,它会 fork 到该轮次末尾,在 client 端递增继承标题并打开子会话。fork 或改名失败时源会话保持选中([决策](../../../.agents/notes/implemented/bug-fix/2026-08-02-message-fork-actions-require-completed-turn-tail.md))。
|
|
62
|
-
- **已发送的 user 消息无法编辑**:user 气泡保留时钟和复制;分支只存在于 assistant 回答之下([决策](../../../.agents/notes/implemented/simplification/2026-08-06-user-bubbles-drop-the-branch-action.md))。编辑功能要与其背后的能力一起回归:既需要针对已定稿 user 消息的 client 变更,也需要 host 侧对已经消费过它的轮次给出行为([决策](../../../.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.md))。
|
|
61
|
+
- **assistant 逐消息分页是预留 slot**:设计中已有图稿,尚未实现。已定稿的内容 IconActions 行(复制/时钟/分支)只挂在每个已结束轮次中最后一条带 text 内容的 assistant 下;轮次中间的叙述、纯 Think 节点,以及仍在产出步骤的轮次里的所有节点都不带 chrome。除非该消息同时也是已完成轮次的最后一个 transcript 节点,否则分支保持禁用;启用后,它会 fork 到该轮次末尾,在 client 端递增继承标题并打开子会话。fork 或改名失败时源会话保持选中([决策](../../../.agents/notes/implemented/bug-fix/2026-08-02-message-fork-actions-require-completed-turn-tail.zh.md))。
|
|
62
|
+
- **已发送的 user 消息无法编辑**:user 气泡保留时钟和复制;分支只存在于 assistant 回答之下([决策](../../../.agents/notes/implemented/simplification/2026-08-06-user-bubbles-drop-the-branch-action.zh.md))。编辑功能要与其背后的能力一起回归:既需要针对已定稿 user 消息的 client 变更,也需要 host 侧对已经消费过它的轮次给出行为([决策](../../../.agents/notes/implemented/simplification/2026-07-31-drop-user-message-edit-stub.zh.md))。
|
|
63
63
|
- **others 工具行的闪光图标是手绘近似版本**:无法在本地导出设计字形的矢量几何;等到存在精确导出后再将其提升到 ui-primitives。
|
|
64
64
|
- **审批面板的「始终允许此类」暂缓**:持久授权需要授权存储设计;今天只能回答允许一次/拒绝。
|
|
65
65
|
- **TodoPanel 将过长条目截成单行省略号**:figma 条没有换行或展开入口,完整文本无法在行内读完。
|
package/lib/client.js
CHANGED
|
@@ -2893,14 +2893,52 @@ window.__ModuleLoader__.load({
|
|
|
2893
2893
|
const whole = Math.round(s);
|
|
2894
2894
|
return `${Math.floor(whole / 60)}m${whole % 60}s`;
|
|
2895
2895
|
}
|
|
2896
|
+
/** Round a cache-read ratio to an integer percentage, with positive ties rounded up. */
|
|
2897
|
+
function roundedIntegerPercent(cacheReadTokens, denominator) {
|
|
2898
|
+
const denominatorQuotient = Math.floor(denominator / 200);
|
|
2899
|
+
const denominatorRemainder = denominator % 200;
|
|
2900
|
+
let lower = 0;
|
|
2901
|
+
let upper = 100;
|
|
2902
|
+
while (lower < upper) {
|
|
2903
|
+
const candidate = Math.floor((lower + upper + 1) / 2);
|
|
2904
|
+
const factor = candidate * 2 - 1;
|
|
2905
|
+
if (cacheReadTokens >= factor * denominatorQuotient + Math.ceil(factor * denominatorRemainder / 200)) lower = candidate;
|
|
2906
|
+
else upper = candidate - 1;
|
|
2907
|
+
}
|
|
2908
|
+
return lower;
|
|
2909
|
+
}
|
|
2896
2910
|
/**
|
|
2897
|
-
*
|
|
2911
|
+
* Display-ready cache-hit share of prompt-side input over the whole durable log.
|
|
2898
2912
|
* @param usage - the session's token-usage projection value.
|
|
2899
|
-
* @returns
|
|
2913
|
+
* @returns integer text when integer rounding stays below 100, otherwise the
|
|
2914
|
+
* minimum decimal precision that still rounds below 100; a full hit returns
|
|
2915
|
+
* 100, and no billed input returns null.
|
|
2900
2916
|
*/
|
|
2901
2917
|
function cacheHitPercent(usage) {
|
|
2902
2918
|
const denominator = billedInputTokens(usage);
|
|
2903
|
-
|
|
2919
|
+
if (denominator === 0) return null;
|
|
2920
|
+
const missedInputTokens = usage.uncachedInputTokens + usage.cacheWriteTokens;
|
|
2921
|
+
if (missedInputTokens === 0) return "100";
|
|
2922
|
+
const integerPercent = roundedIntegerPercent(usage.cacheReadTokens, denominator);
|
|
2923
|
+
if (integerPercent < 100) return String(integerPercent);
|
|
2924
|
+
let decimalPlaces = 1;
|
|
2925
|
+
let scaledDoubleGap = missedInputTokens * 200;
|
|
2926
|
+
const denominatorTens = Math.floor(denominator / 10);
|
|
2927
|
+
while (scaledDoubleGap <= denominatorTens) {
|
|
2928
|
+
scaledDoubleGap *= 10;
|
|
2929
|
+
decimalPlaces += 1;
|
|
2930
|
+
}
|
|
2931
|
+
const denominatorOnes = denominator % 10;
|
|
2932
|
+
let roundedLoss = 5;
|
|
2933
|
+
for (let loss = 1; loss < 5; loss += 1) {
|
|
2934
|
+
const factor = loss * 2 + 1;
|
|
2935
|
+
const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10);
|
|
2936
|
+
if (scaledDoubleGap <= threshold) {
|
|
2937
|
+
roundedLoss = loss;
|
|
2938
|
+
break;
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
return `99.${"9".repeat(decimalPlaces - 1)}${10 - roundedLoss}`;
|
|
2904
2942
|
}
|
|
2905
2943
|
/**
|
|
2906
2944
|
* Sum the three disjoint prompt-side billing buckets.
|
|
@@ -3484,6 +3522,41 @@ window.__ModuleLoader__.load({
|
|
|
3484
3522
|
textRefs: [],
|
|
3485
3523
|
hint: null
|
|
3486
3524
|
};
|
|
3525
|
+
/**
|
|
3526
|
+
* Resolve one edit's range from the record taken before it applied.
|
|
3527
|
+
* A selection the edit replaces is the range outright. A caret delete replaces
|
|
3528
|
+
* nothing and reports the bare caret, so the removed span is whatever the draft
|
|
3529
|
+
* lost, on the side `inputType` names — measured, because one caret gesture can
|
|
3530
|
+
* remove a multi-unit grapheme, a word, or a line.
|
|
3531
|
+
* @param pending - record taken at `beforeinput`, null when none was seen.
|
|
3532
|
+
* @param prevLength - length of the draft the edit applied to.
|
|
3533
|
+
* @param nextLength - length of the resulting draft.
|
|
3534
|
+
* @returns the exact range, or undefined when the record cannot describe this
|
|
3535
|
+
* edit and the machine's diff scan has to recover it.
|
|
3536
|
+
*/
|
|
3537
|
+
function editRangeOf(pending, prevLength, nextLength) {
|
|
3538
|
+
if (pending === null || pending.draftLength !== prevLength) return void 0;
|
|
3539
|
+
const { start, end, inputType } = pending;
|
|
3540
|
+
if (start > end || end > prevLength) return void 0;
|
|
3541
|
+
const insertedLength = nextLength - prevLength + (end - start);
|
|
3542
|
+
if (insertedLength >= 0) return {
|
|
3543
|
+
start,
|
|
3544
|
+
end,
|
|
3545
|
+
insertedLength
|
|
3546
|
+
};
|
|
3547
|
+
if (start !== end) return void 0;
|
|
3548
|
+
const removed = prevLength - nextLength;
|
|
3549
|
+
if (inputType.endsWith("Backward")) return removed <= start ? {
|
|
3550
|
+
start: start - removed,
|
|
3551
|
+
end: start,
|
|
3552
|
+
insertedLength: 0
|
|
3553
|
+
} : void 0;
|
|
3554
|
+
if (inputType.endsWith("Forward")) return start + removed <= prevLength ? {
|
|
3555
|
+
start,
|
|
3556
|
+
end: start + removed,
|
|
3557
|
+
insertedLength: 0
|
|
3558
|
+
} : void 0;
|
|
3559
|
+
}
|
|
3487
3560
|
function InputBar({ useSession, useInput, inputActions, keyboard, addImages, removeImage, draftImages, resolveSubmitMode, toggleCommandMenu, stop, command, t, renderSlot, useNotices, useLexicon, useMenuLauncher, useProjection, sessionId, variant, disabled: inert = false, blocked, workspacePickerOpen = false, onRequestWorkspace, placeholder, accessory, overlay, leftItems, rightItems, footer }) {
|
|
3488
3561
|
const input = useInput((s) => s);
|
|
3489
3562
|
const notice = useNotices((s) => s);
|
|
@@ -3621,6 +3694,28 @@ window.__ModuleLoader__.load({
|
|
|
3621
3694
|
start: el.selectionStart ?? 0,
|
|
3622
3695
|
end: el.selectionEnd ?? el.selectionStart ?? 0
|
|
3623
3696
|
});
|
|
3697
|
+
const pendingEditRef = (0, react.useRef)(null);
|
|
3698
|
+
(0, react.useEffect)(() => {
|
|
3699
|
+
const el = inputRef.current;
|
|
3700
|
+
if (el === null) return;
|
|
3701
|
+
const onBeforeInput = (e) => {
|
|
3702
|
+
if (!e.inputType.startsWith("insert") && !e.inputType.startsWith("delete")) {
|
|
3703
|
+
pendingEditRef.current = null;
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
const { start, end } = selectionOf(el);
|
|
3707
|
+
pendingEditRef.current = {
|
|
3708
|
+
start,
|
|
3709
|
+
end,
|
|
3710
|
+
draftLength: el.value.length,
|
|
3711
|
+
inputType: e.inputType
|
|
3712
|
+
};
|
|
3713
|
+
};
|
|
3714
|
+
el.addEventListener("beforeinput", onBeforeInput);
|
|
3715
|
+
return () => {
|
|
3716
|
+
el.removeEventListener("beforeinput", onBeforeInput);
|
|
3717
|
+
};
|
|
3718
|
+
}, []);
|
|
3624
3719
|
const onKeyDown = (e) => {
|
|
3625
3720
|
if (workspaceTrigger) {
|
|
3626
3721
|
if (e.key === "Enter" || e.key === " ") {
|
|
@@ -3692,8 +3787,10 @@ window.__ModuleLoader__.load({
|
|
|
3692
3787
|
if (keyboard === void 0 || locked) return;
|
|
3693
3788
|
if (machineBusy) return;
|
|
3694
3789
|
const next = e.target.value;
|
|
3790
|
+
const pending = pendingEditRef.current;
|
|
3791
|
+
pendingEditRef.current = null;
|
|
3695
3792
|
safariNativeShrinkRef.current = safari && next.length < draft.length;
|
|
3696
|
-
keyboard.setDraft(next);
|
|
3793
|
+
keyboard.setDraft(next, editRangeOf(pending, draft.length, next.length));
|
|
3697
3794
|
keyboard.track(next, e.target.selectionStart ?? next.length);
|
|
3698
3795
|
};
|
|
3699
3796
|
const onCopyOrCut = (e, cut) => {
|
|
@@ -3810,10 +3907,11 @@ window.__ModuleLoader__.load({
|
|
|
3810
3907
|
at: chip.offset,
|
|
3811
3908
|
kind: "chip",
|
|
3812
3909
|
chip
|
|
3813
|
-
})), ...deco.textRefs.map((ref) => ({
|
|
3910
|
+
})), ...deco.textRefs.map((ref, ordinal) => ({
|
|
3814
3911
|
at: ref.start,
|
|
3815
3912
|
kind: "text-ref",
|
|
3816
|
-
ref
|
|
3913
|
+
ref,
|
|
3914
|
+
ordinal
|
|
3817
3915
|
}))].sort((a, b) => a.at - b.at);
|
|
3818
3916
|
for (const b of boundaries) {
|
|
3819
3917
|
if (b.at < cursor) continue;
|
|
@@ -3856,7 +3954,7 @@ window.__ModuleLoader__.load({
|
|
|
3856
3954
|
className: InputBar_module_css_default.textRefIcon
|
|
3857
3955
|
})]
|
|
3858
3956
|
}), text.slice(1)] }) : text
|
|
3859
|
-
}, `ref-${b.
|
|
3957
|
+
}, `ref-${b.ordinal}`));
|
|
3860
3958
|
cursor = b.ref.end;
|
|
3861
3959
|
}
|
|
3862
3960
|
}
|
|
@@ -5351,7 +5449,7 @@ window.__ModuleLoader__.load({
|
|
|
5351
5449
|
});
|
|
5352
5450
|
//#endregion
|
|
5353
5451
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-conversation/src/client/chat/ChatView.module.css.mjs
|
|
5354
|
-
const css$11 = ".Md3f7G_root{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative}.Md3f7G_scroll{min-height:0;padding:16px calc(var(--dsh-composer-side-clearance) + 16px);flex:auto;overflow-y:auto}[data-conversation-scroll] .Md3f7G_root{flex:none;height:auto;min-height:auto}[data-conversation-scroll] .Md3f7G_scroll{flex:none;min-height:auto;overflow:visible}.Md3f7G_column{max-width:var(--dsh-chat-content-width);flex-direction:column;gap:16px;width:100%;margin:0 auto;display:flex}.Md3f7G_flowItem{min-width:0}.Md3f7G_flowItem:empty{display:none}.Md3f7G_callRow{border-radius:6px}.Md3f7G_turnStatus{height:26px;font:var(--dsw-font-s-strong-14);white-space:nowrap;background:linear-gradient(90deg, var(--dsw-static-deepseek-500) 0%, var(--dsw-static-deepseek-500) 40%, var(--dsw-static-deepseek-200) 50%, var(--dsw-static-deepseek-500) 60%, var(--dsw-static-deepseek-500) 100%);color:#0000;-webkit-text-fill-color:transparent;background-position:100% 0;background-size:250% 100%;-webkit-background-clip:text;background-clip:text;flex:none;align-self:flex-start;align-items:center;animation:1.8s linear infinite Md3f7G_dsh-turn-status-shimmer;display:inline-flex}.Md3f7G_turnStatusClock{font:var(--dsw-font-xs-13);font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption);-webkit-text-fill-color:var(--dsw-alias-label-caption);margin-left:8px;font-weight:400}@keyframes Md3f7G_dsh-turn-status-shimmer{to{background-position:0 0}}@media (prefers-reduced-motion:reduce){.Md3f7G_turnStatus{background-position:0 0;background-size:100% 100%;animation:none}}.Md3f7G_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.Md3f7G_openError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.Md3f7G_older{justify-content:center;display:flex}.Md3f7G_older button{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover-solid);cursor:pointer;border:none;border-radius:14px;padding:4px 12px;font-size:12px}.Md3f7G_older button:disabled{cursor:default;opacity:.6}.Md3f7G_toBottomSlot{z-index:8;height:0;padding-right:max(0px, calc((100% - var(--dsh-chat-content-width)) / 2));pointer-events:none;justify-content:flex-end;display:flex;position:sticky;bottom:16px}[data-conversation-scroll] .Md3f7G_toBottomSlot{bottom:calc(var(--dsh-composer-height,152px) + 16px)}.Md3f7G_toBottom{border:1px solid var(--dsw-alias-border-l2);width:34px;height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-floating-fill);box-shadow:var(--dsw-shadow-lv2);cursor:pointer;pointer-events:auto;border-radius:100px;justify-content:center;align-items:center;margin-top:-34px;padding:0;display:flex}.Md3f7G_toBottom:hover{background:var(--dsw-alias-button-floating-hover)}.Md3f7G_modalAction{min-width:72px}";
|
|
5452
|
+
const css$11 = ".Md3f7G_root{flex-direction:column;flex:auto;min-height:0;display:flex;position:relative}.Md3f7G_scroll{min-height:0;padding:16px calc(var(--dsh-composer-side-clearance) + 16px);flex:auto;overflow-y:auto;container-type:inline-size}[data-conversation-scroll] .Md3f7G_root{flex:none;height:auto;min-height:auto}[data-conversation-scroll] .Md3f7G_scroll{flex:none;min-height:auto;overflow:visible}.Md3f7G_column{max-width:var(--dsh-chat-content-width);flex-direction:column;gap:16px;width:100%;margin:0 auto;display:flex}.Md3f7G_flowItem{min-width:0}.Md3f7G_flowItem:empty{display:none}.Md3f7G_callRow{border-radius:6px}.Md3f7G_turnStatus{height:26px;font:var(--dsw-font-s-strong-14);white-space:nowrap;background:linear-gradient(90deg, var(--dsw-static-deepseek-500) 0%, var(--dsw-static-deepseek-500) 40%, var(--dsw-static-deepseek-200) 50%, var(--dsw-static-deepseek-500) 60%, var(--dsw-static-deepseek-500) 100%);color:#0000;-webkit-text-fill-color:transparent;background-position:100% 0;background-size:250% 100%;-webkit-background-clip:text;background-clip:text;flex:none;align-self:flex-start;align-items:center;animation:1.8s linear infinite Md3f7G_dsh-turn-status-shimmer;display:inline-flex}.Md3f7G_turnStatusClock{font:var(--dsw-font-xs-13);font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-caption);-webkit-text-fill-color:var(--dsw-alias-label-caption);margin-left:8px;font-weight:400}@keyframes Md3f7G_dsh-turn-status-shimmer{to{background-position:0 0}}@media (prefers-reduced-motion:reduce){.Md3f7G_turnStatus{background-position:0 0;background-size:100% 100%;animation:none}}.Md3f7G_hint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.Md3f7G_openError{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.Md3f7G_older{justify-content:center;display:flex}.Md3f7G_older button{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover-solid);cursor:pointer;border:none;border-radius:14px;padding:4px 12px;font-size:12px}.Md3f7G_older button:disabled{cursor:default;opacity:.6}.Md3f7G_toBottomSlot{z-index:8;height:0;padding-right:max(0px, calc((100% - var(--dsh-chat-content-width)) / 2));pointer-events:none;justify-content:flex-end;display:flex;position:sticky;bottom:16px}[data-conversation-scroll] .Md3f7G_toBottomSlot{bottom:calc(var(--dsh-composer-height,152px) + 16px)}.Md3f7G_toBottom{border:1px solid var(--dsw-alias-border-l2);width:34px;height:34px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-floating-fill);box-shadow:var(--dsw-shadow-lv2);cursor:pointer;pointer-events:auto;border-radius:100px;justify-content:center;align-items:center;margin-top:-34px;padding:0;display:flex}.Md3f7G_toBottom:hover{background:var(--dsw-alias-button-floating-hover)}.Md3f7G_modalAction{min-width:72px}";
|
|
5355
5453
|
const tagId$11 = "@deepseek-ai/dsh-client-ui-conversation/ChatView.module.css";
|
|
5356
5454
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$11) + "]") === null) {
|
|
5357
5455
|
const tag = document.createElement("style");
|
|
@@ -7017,7 +7115,7 @@ window.__ModuleLoader__.load({
|
|
|
7017
7115
|
}
|
|
7018
7116
|
//#endregion
|
|
7019
7117
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css.mjs
|
|
7020
|
-
const css$6 = ".wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:\"\";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.wSkVaW_headerUtilities{flex:none;align-items:center;gap:8px;margin-left:20px;display:flex}.wSkVaW_headerUtilities:empty{display:none}.wSkVaW_tabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.wSkVaW_tab{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.wSkVaW_tab:after{content:\"\";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_tabActive{color:var(--dsw-alias-state-business-primary)}.wSkVaW_tabActive:after{background:var(--dsw-alias-state-business-primary)}.wSkVaW_viewArea{flex-direction:column;flex:1;min-height:0;display:flex}.wSkVaW_composerStack{--dsh-composer-stack-gap:6px;gap:var(--dsh-composer-stack-gap);flex-direction:column;display:flex}.wSkVaW_composerSeat{--dsh-composer-text-max-height:336px;flex-direction:column;flex:none;display:flex}.wSkVaW_root[data-phase=active]{overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_header{flex:none}.wSkVaW_scrollBody{scrollbar-gutter:stable;flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden auto}.wSkVaW_root[data-phase=active] .wSkVaW_viewArea{flex:1 0 auto;min-height:auto}.wSkVaW_root[data-phase=active] .wSkVaW_composerSeat{z-index:7;background:linear-gradient(180deg, color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px, var(--dsw-alias-bg-base) 36px);position:sticky;bottom:0}.wSkVaW_scrollBody:has([data-conversation-composer-overlay]){scrollbar-gutter:auto;position:relative;overflow:hidden auto}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>[data-slot=conversation\\.session]>.wSkVaW_viewArea{flex:1 1 0;min-height:0;overflow:hidden}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>.wSkVaW_composerSeat{right:var(--dsh-scrollbar-width);position:absolute;bottom:0;left:0}.wSkVaW_composerHero{width:min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);z-index:1;align-self:center;gap:8px;padding-bottom:32px;position:relative}.wSkVaW_heroGlow{z-index:-1;aspect-ratio:1051/468;pointer-events:none;width:135.438%;position:absolute;bottom:92px;left:50%;transform:translate(-50%,50%)}.wSkVaW_heroWorkspaceRow{align-items:center;gap:2px;min-width:0;margin-top:4px;padding-left:20px;display:flex}.wSkVaW_root[data-phase=hero] .wSkVaW_scrollBody{justify-content:center;overflow-y:auto}.wSkVaW_root[data-phase=settling] .wSkVaW_composerSeat{visibility:hidden}";
|
|
7118
|
+
const css$6 = ".wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:\"\";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumbSubagent{font-size:12px;line-height:18px}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.wSkVaW_headerUtilities{flex:none;align-items:center;gap:8px;margin-left:20px;display:flex}.wSkVaW_headerUtilities:empty{display:none}.wSkVaW_tabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.wSkVaW_tab{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.wSkVaW_tab:after{content:\"\";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_tabActive{color:var(--dsw-alias-state-business-primary)}.wSkVaW_tabActive:after{background:var(--dsw-alias-state-business-primary)}.wSkVaW_viewArea{flex-direction:column;flex:1;min-height:0;display:flex}.wSkVaW_composerStack{--dsh-composer-stack-gap:6px;gap:var(--dsh-composer-stack-gap);flex-direction:column;display:flex}.wSkVaW_composerSeat{--dsh-composer-text-max-height:336px;flex-direction:column;flex:none;display:flex}.wSkVaW_root[data-phase=active]{overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_header{flex:none}.wSkVaW_scrollBody{scrollbar-gutter:stable;flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden auto}.wSkVaW_root[data-phase=active] .wSkVaW_viewArea{flex:1 0 auto;min-height:auto}.wSkVaW_root[data-phase=active] .wSkVaW_composerSeat{z-index:7;background:linear-gradient(180deg, color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px, var(--dsw-alias-bg-base) 36px);position:sticky;bottom:0}.wSkVaW_scrollBody:has([data-conversation-composer-overlay]){scrollbar-gutter:auto;position:relative;overflow:hidden auto}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>[data-slot=conversation\\.session]>.wSkVaW_viewArea{flex:1 1 0;min-height:0;overflow:hidden}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>.wSkVaW_composerSeat{right:var(--dsh-scrollbar-width);position:absolute;bottom:0;left:0}.wSkVaW_composerHero{width:min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);z-index:1;align-self:center;gap:8px;padding-bottom:32px;position:relative}.wSkVaW_heroGlow{z-index:-1;aspect-ratio:1051/468;pointer-events:none;width:135.438%;position:absolute;bottom:92px;left:50%;transform:translate(-50%,50%)}.wSkVaW_heroWorkspaceRow{align-items:center;gap:2px;min-width:0;margin-top:4px;padding-left:20px;display:flex}.wSkVaW_root[data-phase=hero] .wSkVaW_scrollBody{justify-content:center;overflow-y:auto}.wSkVaW_root[data-phase=settling] .wSkVaW_composerSeat{visibility:hidden}";
|
|
7021
7119
|
const tagId$6 = "@deepseek-ai/dsh-client-ui-conversation/ConversationRoot.module.css";
|
|
7022
7120
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
|
|
7023
7121
|
const tag = document.createElement("style");
|
|
@@ -7034,6 +7132,7 @@ window.__ModuleLoader__.load({
|
|
|
7034
7132
|
"crumbCurrent": "wSkVaW_crumbCurrent",
|
|
7035
7133
|
"crumbSeg": "wSkVaW_crumbSeg",
|
|
7036
7134
|
"crumbSep": "wSkVaW_crumbSep",
|
|
7135
|
+
"crumbSubagent": "wSkVaW_crumbSubagent",
|
|
7037
7136
|
"crumbs": "wSkVaW_crumbs",
|
|
7038
7137
|
"header": "wSkVaW_header",
|
|
7039
7138
|
"headerActions": "wSkVaW_headerActions",
|
|
@@ -7200,7 +7299,8 @@ window.__ModuleLoader__.load({
|
|
|
7200
7299
|
if (summary === void 0) break;
|
|
7201
7300
|
chain.unshift({
|
|
7202
7301
|
id: summary.id,
|
|
7203
|
-
displayTitle: summary.displayTitle
|
|
7302
|
+
displayTitle: summary.displayTitle,
|
|
7303
|
+
subagent: summary.origin === "subagent"
|
|
7204
7304
|
});
|
|
7205
7305
|
if (summary.origin !== "subagent") break;
|
|
7206
7306
|
cursor = summary.parentId;
|
|
@@ -7237,20 +7337,29 @@ window.__ModuleLoader__.load({
|
|
|
7237
7337
|
"aria-label": t("session.hierarchy"),
|
|
7238
7338
|
children: [ancestry.map((summary, index) => {
|
|
7239
7339
|
const last = index === ancestry.length - 1;
|
|
7340
|
+
const title = (0, react_jsx_runtime.jsx)("button", {
|
|
7341
|
+
type: "button",
|
|
7342
|
+
className: clsx(ConversationRoot_module_css_default.crumb, summary.subagent && ConversationRoot_module_css_default.crumbSubagent, last && ConversationRoot_module_css_default.crumbCurrent),
|
|
7343
|
+
disabled: last,
|
|
7344
|
+
onClick: () => {
|
|
7345
|
+
open(summary.id);
|
|
7346
|
+
},
|
|
7347
|
+
children: summary.displayTitle
|
|
7348
|
+
});
|
|
7349
|
+
const lineage = last || summary.subagent;
|
|
7350
|
+
const lineageOwner = {
|
|
7351
|
+
lineageSessionId: summary.id,
|
|
7352
|
+
displayTitle: summary.displayTitle,
|
|
7353
|
+
...last ? {} : { openTitle: () => {
|
|
7354
|
+
open(summary.id);
|
|
7355
|
+
} }
|
|
7356
|
+
};
|
|
7240
7357
|
return (0, react_jsx_runtime.jsxs)("span", {
|
|
7241
7358
|
className: ConversationRoot_module_css_default.crumbSeg,
|
|
7242
7359
|
children: [index > 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
7243
7360
|
className: ConversationRoot_module_css_default.crumbSep,
|
|
7244
7361
|
children: "/"
|
|
7245
|
-
}), (0, react_jsx_runtime.
|
|
7246
|
-
type: "button",
|
|
7247
|
-
className: clsx(ConversationRoot_module_css_default.crumb, last && ConversationRoot_module_css_default.crumbCurrent),
|
|
7248
|
-
disabled: last,
|
|
7249
|
-
onClick: () => {
|
|
7250
|
-
open(summary.id);
|
|
7251
|
-
},
|
|
7252
|
-
children: summary.displayTitle
|
|
7253
|
-
})]
|
|
7362
|
+
}), lineage ? summary.subagent ? renderSlot("conversation.session.header.lineage", lineageOwner, { fallback: title }) : (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [title, renderSlot("conversation.session.header.lineage", lineageOwner, { fallback: null })] }) : title]
|
|
7254
7363
|
}, summary.id);
|
|
7255
7364
|
}), ancestry.length === 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
7256
7365
|
className: ConversationRoot_module_css_default.crumbCurrent,
|
|
@@ -8888,9 +8997,6 @@ window.__ModuleLoader__.load({
|
|
|
8888
8997
|
if (location?.kind !== "turn" && location?.kind !== "step") return 0;
|
|
8889
8998
|
return location.turn.steps.at(-1)?.step ?? 0;
|
|
8890
8999
|
}
|
|
8891
|
-
function retryTurn(event) {
|
|
8892
|
-
return event.type === "llm/retry" || event.type === "llm/retry-started" ? event.data.turn : void 0;
|
|
8893
|
-
}
|
|
8894
9000
|
function failureFrom(match) {
|
|
8895
9001
|
if (match.event.type !== "turn/end" || match.event.data.reason.kind !== "error") return void 0;
|
|
8896
9002
|
const failure = match.event.data.reason.error;
|
|
@@ -8906,14 +9012,16 @@ window.__ModuleLoader__.load({
|
|
|
8906
9012
|
if (end?.event.type !== "turn/end") return void 0;
|
|
8907
9013
|
const failure = failureFrom(end);
|
|
8908
9014
|
if (failure === void 0) return void 0;
|
|
8909
|
-
const turn = end.event.data.turn;
|
|
8910
9015
|
return {
|
|
8911
|
-
turn,
|
|
8912
|
-
hidden: context.matches.some((match) => retryTurn(match.event) === turn),
|
|
9016
|
+
turn: end.event.data.turn,
|
|
8913
9017
|
failure
|
|
8914
9018
|
};
|
|
8915
9019
|
}
|
|
8916
|
-
/**
|
|
9020
|
+
/**
|
|
9021
|
+
* Terminal turn failure Definition. Retries run inside the failing turn, so the
|
|
9022
|
+
* turn's `llm/retry` history never suppresses this terminal row; the model-retry
|
|
9023
|
+
* node renders that history separately.
|
|
9024
|
+
*/
|
|
8917
9025
|
const turnErrorDefinition = {
|
|
8918
9026
|
kind: "turn-error",
|
|
8919
9027
|
target: "chat",
|
|
@@ -8926,29 +9034,18 @@ window.__ModuleLoader__.load({
|
|
|
8926
9034
|
id: String(event.data.turn),
|
|
8927
9035
|
role: "update"
|
|
8928
9036
|
};
|
|
8929
|
-
|
|
8930
|
-
return turn === void 0 ? null : {
|
|
8931
|
-
id: String(turn),
|
|
8932
|
-
role: "update"
|
|
8933
|
-
};
|
|
9037
|
+
return null;
|
|
8934
9038
|
},
|
|
8935
9039
|
start: (_context, match) => {
|
|
8936
9040
|
if (match.event.type !== "turn/start") throw new Error("turn-error start requires turn/start");
|
|
8937
|
-
return {
|
|
8938
|
-
turn: match.event.data.turn,
|
|
8939
|
-
hidden: false
|
|
8940
|
-
};
|
|
9041
|
+
return { turn: match.event.data.turn };
|
|
8941
9042
|
},
|
|
8942
9043
|
update: (context, match) => {
|
|
8943
9044
|
const failure = failureFrom(match);
|
|
8944
|
-
|
|
9045
|
+
return failure === void 0 ? context.state : {
|
|
8945
9046
|
...context.state,
|
|
8946
9047
|
failure
|
|
8947
9048
|
};
|
|
8948
|
-
return retryTurn(match.event) === context.state.turn ? {
|
|
8949
|
-
...context.state,
|
|
8950
|
-
hidden: true
|
|
8951
|
-
} : context.state;
|
|
8952
9049
|
},
|
|
8953
9050
|
buildViewNode: (context) => {
|
|
8954
9051
|
const state = context.state ?? fallbackState(context);
|
|
@@ -8963,9 +9060,7 @@ window.__ModuleLoader__.load({
|
|
|
8963
9060
|
message: failure.message,
|
|
8964
9061
|
...failure.code === void 0 ? {} : { code: failure.code }
|
|
8965
9062
|
};
|
|
8966
|
-
|
|
8967
|
-
const current = context.current.get("chat");
|
|
8968
|
-
return current === void 0 || current === null ? null : chatNode(context, "turn-error", node.seq, node, { visibility: "hidden" });
|
|
9063
|
+
return chatNode(context, "turn-error", node.seq, node);
|
|
8969
9064
|
}
|
|
8970
9065
|
};
|
|
8971
9066
|
/**
|
|
@@ -9345,7 +9440,7 @@ window.__ModuleLoader__.load({
|
|
|
9345
9440
|
}
|
|
9346
9441
|
//#endregion
|
|
9347
9442
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-conversation/src/client/chat/AssistantMarkdown.module.css.mjs
|
|
9348
|
-
const css$2 = ".Sxvs8a_root{color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.Sxvs8a_body{flex-direction:column;gap:16px;display:flex}.Sxvs8a_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.Sxvs8a_actions{margin-top:16px;margin-left:-6px}";
|
|
9443
|
+
const css$2 = ".Sxvs8a_root{color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.Sxvs8a_body{flex-direction:column;gap:16px;display:flex}.Sxvs8a_body .md-table-wide{--dsh-table-spare:max(0px, calc((100cqw - var(--dsh-chat-content-width)) / 2));--dsh-table-lead:calc(var(--dsh-table-spare) + min(var(--dsh-chat-content-width), 100cqw) - 100%);box-sizing:border-box;width:calc(100% + var(--dsh-table-lead) + var(--dsh-table-spare));max-width:none;margin-left:calc(-1 * var(--dsh-table-lead));padding-left:var(--dsh-table-lead)}.Sxvs8a_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.Sxvs8a_actions{margin-top:16px;margin-left:-6px}";
|
|
9349
9444
|
const tagId$2 = "@deepseek-ai/dsh-client-ui-conversation/AssistantMarkdown.module.css";
|
|
9350
9445
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
9351
9446
|
const tag = document.createElement("style");
|
|
@@ -9951,6 +10046,10 @@ window.__ModuleLoader__.load({
|
|
|
9951
10046
|
name: "conversation.session.header",
|
|
9952
10047
|
locale: NS,
|
|
9953
10048
|
children: {
|
|
10049
|
+
"conversation.session.header.lineage": {
|
|
10050
|
+
kind: "single",
|
|
10051
|
+
scope: "session"
|
|
10052
|
+
},
|
|
9954
10053
|
"conversation.session.header.actions": {
|
|
9955
10054
|
kind: "list",
|
|
9956
10055
|
scope: "session"
|
|
@@ -44,11 +44,13 @@ export declare function formatTokens(n: number): string;
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function formatDuration(ms: number): string;
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Display-ready cache-hit share of prompt-side input over the whole durable log.
|
|
48
48
|
* @param usage - the session's token-usage projection value.
|
|
49
|
-
* @returns
|
|
49
|
+
* @returns integer text when integer rounding stays below 100, otherwise the
|
|
50
|
+
* minimum decimal precision that still rounds below 100; a full hit returns
|
|
51
|
+
* 100, and no billed input returns null.
|
|
50
52
|
*/
|
|
51
|
-
export declare function cacheHitPercent(usage: TokenUsageProjection):
|
|
53
|
+
export declare function cacheHitPercent(usage: TokenUsageProjection): string | null;
|
|
52
54
|
/**
|
|
53
55
|
* Sum the three disjoint prompt-side billing buckets.
|
|
54
56
|
* @param usage - the session's token-usage projection value.
|
|
@@ -73,6 +73,16 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
|
73
73
|
kind: 'single';
|
|
74
74
|
scope: 'session';
|
|
75
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* One breadcrumb title and its lineage controls. The render site keeps
|
|
78
|
+
* the ordinary title as fallback; an occupant receives plain title data
|
|
79
|
+
* and may replace a subagent title with one combined navigation control.
|
|
80
|
+
*/
|
|
81
|
+
'conversation.session.header.lineage': {
|
|
82
|
+
kind: 'single';
|
|
83
|
+
scope: 'session';
|
|
84
|
+
owner: ConversationHeaderLineageOwnerProps;
|
|
85
|
+
};
|
|
76
86
|
/**
|
|
77
87
|
* One button in the session header's action row — the additive way to put
|
|
78
88
|
* a per-session control beside the title without replacing the header.
|
|
@@ -367,6 +377,15 @@ export interface ConversationSessionOwnerProps {
|
|
|
367
377
|
/** Header actions derive their state from the standard session/global kit. */
|
|
368
378
|
export interface ConversationHeaderActionOwnerProps {
|
|
369
379
|
}
|
|
380
|
+
/** Plain breadcrumb data handed to the optional lineage renderer. */
|
|
381
|
+
export interface ConversationHeaderLineageOwnerProps {
|
|
382
|
+
/** Session represented by this breadcrumb title. */
|
|
383
|
+
lineageSessionId: SessionId;
|
|
384
|
+
/** Display title available to a renderer that combines the title with a control. */
|
|
385
|
+
displayTitle: string;
|
|
386
|
+
/** Navigate to an ancestor title when its combined control is clicked. */
|
|
387
|
+
openTitle?: () => void;
|
|
388
|
+
}
|
|
370
389
|
/**
|
|
371
390
|
* The input-region slot currency: dock/left/right entries read
|
|
372
391
|
* the conversation snapshot and the live input state as owner props (both
|
|
@@ -651,7 +670,7 @@ export type ConversationSlotProps = PropsRuntime<'conversation'> & PropsRenderSl
|
|
|
651
670
|
/** Full strict-session body props: per-session store, view ring, and draft mirror. */
|
|
652
671
|
export type ConversationSessionSlotProps = PropsRuntime<'conversation.session'> & PropsRenderSlots<'conversation.view'> & PropsStore<ChatStore> & ConversationSessionInjected;
|
|
653
672
|
/** Full strict-session header props: shared store, tabs/actions render shares, navigation, and locale. */
|
|
654
|
-
export type ConversationSessionHeaderSlotProps = PropsRuntime<'conversation.session.header'> & PropsRenderSlots<'conversation.session.header.actions' | 'conversation.session.header.utilities'> & PropsStore<ChatStore> & ConversationSessionHeaderInjected & PropsLocale<'conversation'>;
|
|
673
|
+
export type ConversationSessionHeaderSlotProps = PropsRuntime<'conversation.session.header'> & PropsRenderSlots<'conversation.session.header.lineage' | 'conversation.session.header.actions' | 'conversation.session.header.utilities'> & PropsStore<ChatStore> & ConversationSessionHeaderInjected & PropsLocale<'conversation'>;
|
|
655
674
|
/** The pending approval carrier the owner dispatches into the composer chain. */
|
|
656
675
|
export type ApprovalWait = PendingWait<'approval'>;
|
|
657
676
|
/**
|
|
@@ -2,13 +2,12 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import type { ConversationNodeDefinition, TurnErrorNode } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
3
|
declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
|
|
4
4
|
interface ChatNodeDataMap {
|
|
5
|
-
/** Terminal turn failure
|
|
5
|
+
/** Terminal turn failure recorded on the turn's end reason. */
|
|
6
6
|
'turn-error': TurnErrorNode;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
interface TurnErrorState {
|
|
10
10
|
readonly turn: number;
|
|
11
|
-
readonly hidden: boolean;
|
|
12
11
|
readonly failure?: {
|
|
13
12
|
readonly seq: number;
|
|
14
13
|
readonly time: number;
|
|
@@ -16,7 +15,11 @@ interface TurnErrorState {
|
|
|
16
15
|
readonly code?: string;
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Terminal turn failure Definition. Retries run inside the failing turn, so the
|
|
20
|
+
* turn's `llm/retry` history never suppresses this terminal row; the model-retry
|
|
21
|
+
* node renders that history separately.
|
|
22
|
+
*/
|
|
20
23
|
export declare const turnErrorDefinition: ConversationNodeDefinition<TurnErrorState>;
|
|
21
24
|
/**
|
|
22
25
|
* Register the terminal Turn-error business contribution.
|
|
@@ -20,7 +20,7 @@ export type { DraftAttachmentId } from './input/contract.ts';
|
|
|
20
20
|
export type { CallId, ChatStoreState, SelectionTarget, ViewTab, } from './contract/views.ts';
|
|
21
21
|
export type { ConversationKey } from './locales.ts';
|
|
22
22
|
export type { AssistantChatData, ChatNode, ChatNodeDataMap, ChatNodeKind, ManualCompactionChatData, RetryChatData, ToolChatData, TurnTailChatData, } from './contract/chat-nodes.ts';
|
|
23
|
-
export type { ChatFileMentions, ChatNodeOwnerProps, ChatNodeViewProps, ChatStore, ChatViewInjected, ChatViewSlotProps, CommandRowOwnerProps, CommandRowProps, ComposerBarInjected, ComposerAttachment, ComposerAttachmentsOwnerProps, ComposerAttachmentsProps, ComposerChainProps, ConversationInjected, ConversationSessionHeaderInjected, ConversationSessionInjected, ConversationSlotProps, ConvViewOwnerProps, ConvViewProps, DetailsInjected, DetailsSlotProps, DetailsToolOwnerProps, EmptyWorkspaceOwnerProps, HeroBrandMarkOwnerProps, MessageImagesOwnerProps, MessageImagesProps, RenderMessageImages, TurnTailOwnerProps, UseChatNodeTurnData, } from './contract/slots.ts';
|
|
23
|
+
export type { ChatFileMentions, ChatNodeOwnerProps, ChatNodeViewProps, ChatStore, ChatViewInjected, ChatViewSlotProps, CommandRowOwnerProps, CommandRowProps, ComposerBarInjected, ComposerAttachment, ComposerAttachmentsOwnerProps, ComposerAttachmentsProps, ComposerChainProps, ConversationInjected, ConversationHeaderLineageOwnerProps, ConversationSessionHeaderInjected, ConversationSessionInjected, ConversationSlotProps, ConvViewOwnerProps, ConvViewProps, DetailsInjected, DetailsSlotProps, DetailsToolOwnerProps, EmptyWorkspaceOwnerProps, HeroBrandMarkOwnerProps, MessageImagesOwnerProps, MessageImagesProps, RenderMessageImages, TurnTailOwnerProps, UseChatNodeTurnData, } from './contract/slots.ts';
|
|
24
24
|
declare module '@deepseek-ai/cordis' {
|
|
25
25
|
interface Context {
|
|
26
26
|
/** The outward face only; the concrete service stays inside this plugin. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-conversation",
|
|
3
3
|
"description": "Conversation domain: skeleton, ordered chat flow, composer with the Host-backed busy-Enter preference, and details host",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -49,59 +49,59 @@
|
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
52
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-client-
|
|
57
|
-
"@deepseek-ai/dsh-client-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-plan-mode": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-tool-todo": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-
|
|
52
|
+
"@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.1-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-llm-retry": "^0.1.1-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-session-stats": "^0.1.1-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-compaction": "^0.1.1-rc.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-goal": "^0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tool-todo": "^0.1.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.2"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@types/react": "~18.3.1",
|
|
77
77
|
"react": "^18.2.0",
|
|
78
78
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
79
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-
|
|
81
|
-
"@deepseek-ai/dsh-
|
|
82
|
-
"@deepseek-ai/dsh-
|
|
83
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
84
|
-
"@deepseek-ai/dsh-client-
|
|
85
|
-
"@deepseek-ai/dsh-client-
|
|
86
|
-
"@deepseek-ai/dsh-client-ui-layout": "^0.1.
|
|
87
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
88
|
-
"@deepseek-ai/dsh-client-ui-
|
|
89
|
-
"@deepseek-ai/dsh-client-ui-
|
|
90
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
91
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
92
|
-
"@deepseek-ai/dsh-
|
|
93
|
-
"@deepseek-ai/dsh-
|
|
94
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
95
|
-
"@deepseek-ai/dsh-
|
|
96
|
-
"@deepseek-ai/dsh-
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
99
|
-
"@deepseek-ai/dsh-session-
|
|
100
|
-
"@deepseek-ai/dsh-token-meter": "^0.1.
|
|
101
|
-
"@deepseek-ai/dsh-tool-todo": "^0.1.
|
|
102
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
103
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
104
|
-
"@deepseek-ai/dsh-
|
|
79
|
+
"@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
|
|
82
|
+
"@deepseek-ai/dsh-brand": "^0.1.1-rc.2",
|
|
83
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
84
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.1-rc.2",
|
|
90
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
|
|
91
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-compaction": "^0.1.1-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-goal": "^0.1.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
95
|
+
"@deepseek-ai/dsh-llm-retry": "^0.1.1-rc.2",
|
|
96
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.1-rc.2",
|
|
97
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.1-rc.2",
|
|
98
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.1-rc.2",
|
|
99
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
|
|
101
|
+
"@deepseek-ai/dsh-tool-todo": "^0.1.1-rc.2",
|
|
102
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
103
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
104
|
+
"@deepseek-ai/dsh-session-stats": "^0.1.1-rc.2"
|
|
105
105
|
},
|
|
106
106
|
"files": [
|
|
107
107
|
"lib/index.js",
|