@deepseek-ai/dsh-client-ui-subagent 0.1.1-rc.2 → 0.1.2-alpha.3

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 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-subagent/README.md
5
- README.md: d4a804a94b0f2cb295fd29c09c7f32385e423495
6
- README.zh.md: 3cec2109cbb5f85617cf69155c5582ee43496f09
5
+ README.md: 3c7d185123d76270844d17dcb0e070e27f33ef7d
6
+ README.zh.md: cf022bb0d121805f9b322e84fb77c6dac412fad0
package/README.md CHANGED
@@ -1,24 +1,91 @@
1
+ ---
2
+ description: "Subagent conversation catalog, continuation routing UI, and '@' reference source for the dsh web client."
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-client-ui-subagent
2
7
 
3
8
  English | [中文](README.zh.md)
4
9
 
5
- Web subagent feature owner: contributes current-title lineage navigation to `conversation.session.header.lineage`, reason-specific read-only replacements to the conversation composer chain, and the existing `@` reference source to `ctx.inputTriggers`.
10
+ ## Summary
11
+
12
+ `dsh-client-ui-subagent` is the web client's subagent conversation feature: users browse and open subagent conversations from the parent session's header, continue them through reason-specific read-only composer states, and reference running children with the `@` source. From the parent session's header, users browse the complete subagent-origin descendant lineage — each row shows mode, running activity, token usage, and active-turn duration — and open any depth with the child's exact address. A one-shot child always opens a read-only composer identifying the transcript as a completed execution record; a continuable child routes follow-up prompts through its FIFO inbox while it runs. Subagent-origin Session rows are omitted from the ordinary sidebar, so the parent header catalog is their navigation entry point.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ The session header keeps the current session title as the lineage breadcrumb and, when the session has subagent descendants, appends a `/` count trigger before the header's action row; the trigger opens the descendant catalog, counts the complete subagent-only lineage, stops at ordinary forks, and shows ongoing activity when any counted descendant is running. Select any depth to open that child's conversation with its exact `{parentSessionId, childSessionId, mode}` address.
29
+
30
+ ### Browsing the tree
31
+
32
+ Rows display mode plus `running`/`inactive` activity and an optional log-backed title; the trailing column stacks total durable provider usage above active-turn duration. Keyboard navigation works with ArrowRight/ArrowLeft to expand and collapse branches and ArrowUp/ArrowDown, Home, End, and Escape to navigate or close the tree. An unlabeled one-shot row falls back to its session id; corrupt, unsupported, or unavailable rows remain readable but disabled.
33
+
34
+ ### Continuing a conversation
35
+
36
+ A continuable child with a live parent keeps the ordinary input chrome: typing and Send stay available while the child runs because every follow-up joins the child's FIFO inbox, and an independent Stop routes through `subagents/interruptByParent`. A continuable child whose exact parent is unavailable and which is not running elects a read-only composer explaining the recovery path; while such a child still runs, the selector yields to the ordinary composer with input and Send disabled but its independent Stop usable.
37
+
38
+ ### The `@` reference source
39
+
40
+ The `@` source remains deliberately separate and inert: candidates are zero-RPC running children from `ctx.sessions.list`, picking one inserts literal `@label ` text, and the codec projects `@label`. It has no command-adjudication hooks and does not resolve labels into continuation addresses.
41
+
42
+ -----
43
+
44
+ <a id="understand-the-implementation"></a>
45
+ ## Understand the implementation
46
+
47
+ <details>
48
+ <summary>Implementation internals — click to expand</summary>
6
49
 
7
- The header lineage renderer reads `subagentsByParent` and session summaries through the standard `useSessions` hook. On an ordinary session it preserves the current title and, when descendants exist, appends `/` plus the descendant-count dropdown before the header action row. Every subagent breadcrumb uses the count trigger's 12px type scale and combines its title with a fixed double chevron; the current breadcrumb keeps primary color and weight 500, while ancestors use tertiary color and weight 400. Hovering a combined control for 150ms opens its direct-parent catalog, ArrowDown remains the keyboard entry path, and clicking an ancestor cancels a pending menu and navigates upward without opening it. Each catalog enables sibling switching and bolds its selected row; a catalog label overrides the session-summary title, and every switcher whose catalog has not loaded requests it automatically. Only the current subagent appends its own descendant-count dropdown. Long breadcrumb titles truncate while the chevron remains visible. The count trigger covers the complete subagent-only descendant lineage, stops at ordinary forks, and shows ongoing activity when any counted descendant is running. The compact tree remains direct-catalog authoritative: continuable and one-shot rows display mode plus `running`/`inactive` activity and an optional log-backed title, while the trailing column stacks total durable provider usage above active-turn duration. Token totals sum the four disjoint `tokenUsage` buckets. Visual duration stays exact to the second below one day, then uses at most two adjacent units—days/hours, approximate months/days, or approximate years/months—while hover and the accessible name retain the exact day/hour/minute/second value. Duration sums completed `subagentTiming` turns, advances once per second only for an open turn on a running child, and freezes after the child becomes inactive; an interrupted open turn is bounded by its same-cut `active.through`, never by newer session metadata. An unlabeled one-shot row falls back to its session id, while corrupt, unsupported, or unavailable rows remain readable but disabled. Each healthy row's `hasChildren` hint determines disclosure before interaction, so known leaves never show an arrow; a catalog level reserves the disclosure column only when at least one healthy row is a branch, allowing branchless levels to start at the leading status marker. Expanding a branch immediately reserves one disabled loading row per known direct descendant, then lazily replaces them with that child's authoritative catalog. Every visible branch is reported to the runtime so membership frames cause a debounced refresh only where the tree is being consumed. Selecting any depth calls `SessionRuntime.openSubagent()` with the row's exact `{parentSessionId, childSessionId, mode}` address. Component-local state owns tree visibility, expanded branches, keyboard focus, and the running-duration clock. ArrowRight/ArrowLeft expand and collapse branches; ArrowUp/ArrowDown, Home, End, and Escape navigate or close the tree; closing returns focus to the trigger. Styling uses tokens only.
50
+ The catalog and composer behavior are specified by the [Web subagent conversations note](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md) and the [current-turn interrupt note](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md).
8
51
 
9
- A one-shot child always elects a read-only composer that identifies the transcript as a completed execution record. A continuable child does so only when its exact parent is unavailable and the child is not running, with copy explaining the recovery path; while such a child still runs, the selector yields to the ordinary composer, whose input and Send action are disabled but whose independent Stop stays usable, and the takeover returns once it stops. A continuable child with a live parent keeps the ordinary input chrome, whose Session routes prompts through `subagent.prompt`: typing and Send stay available while the child runs because every follow-up joins the child's FIFO inbox, while an independent Stop routes through `subagent.interrupt`. This package never receives host context or calls a model-facing tool. The catalog and composer behavior are specified by the [Web subagent conversations Agent Note](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md) and the [current-turn interrupt Agent Note](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md).
52
+ ### Catalog derivation
10
53
 
11
- Subagent-origin Session rows are omitted from the ordinary sidebar, so the parent header catalog is their navigation entry point. Ordinary forks remain in the sidebar.
54
+ The header lineage renderer reads `subagentsByParent` and session summaries through the standard `useSessions` hook. The compact tree remains direct-catalog authoritative: each healthy row's `hasChildren` hint determines disclosure before interaction, a catalog level reserves the disclosure column only when at least one healthy row is a branch, and expanding a branch immediately reserves one disabled loading row per known direct descendant before lazily replacing them with that child's authoritative catalog. Every visible branch is reported to the runtime so membership frames cause a debounced refresh only where the tree is being consumed.
12
55
 
13
- The `@` source remains deliberately separate and inert. Candidates are zero-RPC running children from `ctx.sessions.list`; picking one inserts literal `@label ` text, and the codec projects `@label`. It has no command-adjudication hooks and does not resolve labels into continuation addresses.
56
+ ### Duration and tokens
14
57
 
58
+ Token totals sum the four disjoint `tokenUsage` buckets. Duration sums completed `subagentTiming` turns, advances once per second only for an open turn on a running child, and freezes after the child becomes inactive; an interrupted open turn is bounded by its same-cut `active.through`, never by newer session metadata.
59
+
60
+ ### Composer election
61
+
62
+ One-shot children always elect a read-only composer. A continuable child elects one only when its exact parent is unavailable and the child is not running; otherwise the ordinary composer's Session routes prompts through `subagents/prompt`. This package never receives host context or calls a model-facing tool.
63
+
64
+ </details>
65
+
66
+ -----
67
+
68
+ <a id="further-exploration"></a>
69
+ ## Further Exploration
70
+
71
+ These pages cover the conversation surface, the host seam, and the design notes.
72
+
73
+ - [ui-conversation](../ui-conversation/README.md) — the chat surface hosting the header action and composer chain.
74
+ - [ui-input-trigger](../ui-input-trigger/README.md) — the suggestion machinery hosting the `@` source.
75
+ - [subagent](../../subagent/subagent/README.md) — the host-side capability seam behind continuable children.
76
+ - [Web subagent conversations](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md) — the catalog and composer specification.
77
+ - [Current-turn interrupt](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md) — the independent Stop semantics.
78
+
79
+ -----
80
+
81
+ <a id="model-experience"></a>
15
82
  ## Model Experience
16
83
 
17
84
  ### Subagent label text in the user prompt
18
85
 
19
86
  #### What the model sees
20
87
 
21
- Only the legacy `@` reference source affects model input: a picked candidate reaches the ordinary user message as literal `@label`, without a dedicated block or host-side resolution. Catalog browsing, child navigation, and persisted transcript viewing add no prompt section; accepted continuation content becomes a normal FIFO user message through the host subagent adapter.
88
+ Only the `@` reference source affects model input: a picked candidate reaches the ordinary user message as literal `@label`, without a dedicated block or host-side resolution. Catalog browsing, child navigation, and persisted transcript viewing add no prompt section; accepted continuation content becomes a normal FIFO user message through the host subagent adapter.
22
89
 
23
90
  #### Token effect
24
91
 
@@ -30,5 +97,20 @@ Append-only. This package never edits earlier request tokens.
30
97
 
31
98
  ## Known Limitations and Deferred Work
32
99
 
100
+ <a id="known-limitations-and-deferred-work"></a>
101
+
102
+
103
+ These limits define what the catalog can show and what `@` references mean; they are current package constraints.
104
+
33
105
  - **The catalog has no durable outcome** — activity and timing do not distinguish completion, failure, or cancellation, and the UI exposes no Activation identity; stopping is limited to the composer's current-turn Stop for a running continuable child.
34
106
  - **`@` references remain display-title text** — duplicate or renamed labels are ambiguous, so they intentionally do not acquire continuation semantics.
107
+
108
+ <a id="dev-note"></a>
109
+ ### Dev Note
110
+
111
+ <details>
112
+ <summary>Working context for maintainers — click to expand</summary>
113
+
114
+ None.
115
+
116
+ </details>
package/README.zh.md CHANGED
@@ -1,24 +1,91 @@
1
+ ---
2
+ description: "dsh Web 客户端的 subagent 对话目录、续接路由 UI 与 '@' 引用 source。"
3
+ kind: "package-reference"
4
+ ---
5
+
1
6
  # @deepseek-ai/dsh-client-ui-subagent
2
7
 
3
8
  [English](README.md) | 中文
4
9
 
5
- Web subagent 功能 owner:向 `conversation.session.header.lineage` 贡献当前 title 谱系导航,向会话编辑器链贡献按原因区分的只读替代呈现,并保留注册到 `ctx.inputTriggers` 的既有 `@` 引用 source。
10
+ ## 概述
11
+
12
+ `dsh-client-ui-subagent` 是 Web 客户端的 subagent 对话功能:用户从父会话的页头浏览并打开 subagent 对话,通过按原因区分的只读编辑器状态续接对话,并用 `@` source 引用运行中的 child。用户从父会话的页头浏览完整的 subagent 来源后代谱系——每一行显示 mode、运行活动、token 用量与活跃轮次耗时——并能以子会话的确切地址打开任意深度。one-shot child 始终打开一个把 transcript 说明为已完成执行记录的只读编辑器;可继续 child 在运行期间把后续提示词经其 FIFO inbox 路由。普通侧边栏会省略带 subagent origin 的会话行,因此父级页头目录是它们的导航入口。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 会话页头保留当前会话 title 作为谱系面包屑,并在会话存在 subagent 后代时,于页头操作行之前追加 `/` 数量触发器;触发器打开后代目录,统计仅含 subagent 的完整谱系、在普通 fork 处停止,并在任一计入统计的后代处于 `running` 时显示活动仍在进行。选择任意深度,即可用该子会话的确切 `{parentSessionId, childSessionId, mode}` 地址打开其对话。
29
+
30
+ ### 浏览目录
31
+
32
+ 行显示 mode、`running`/`inactive` 活动状态与由日志支撑的可选 title;尾随列在上行显示提供方的持久化 token 用量总计,在下行显示活跃轮次耗时。键盘导航:ArrowRight/ArrowLeft 展开和折叠分支;ArrowUp/ArrowDown、Home、End 与 Escape 用于导航或关闭树。没有 label 的 one-shot 行回退到其会话 id;损坏、不受支持或不可用的行仍保持可读但禁用。
33
+
34
+ ### 续接对话
35
+
36
+ 确切 parent 存活时,可继续 child 保留普通输入 chrome:child 运行期间输入和 Send 保持可用,因为每条后续消息都会进入 child 的 FIFO inbox,而独立的 Stop 经由 `subagents/interruptByParent` 路由。确切 parent 不可用且 child 未在运行的可继续 child 会选用说明恢复路径的只读编辑器;此类 child 仍在运行期间,selector 会让位给普通编辑器——输入区与 Send 被禁用,但独立的 Stop 保持可用。
37
+
38
+ ### `@` 引用 source
39
+
40
+ `@` source 仍然刻意保持独立且惰性:候选是从 `ctx.sessions.list` 零 RPC 得到的运行中 child;pick 会插入字面文本 `@label `,codec 投影为 `@label`。它不参与命令裁决,也不会把 label 解析成继续执行地址。
41
+
42
+ -----
43
+
44
+ <a id="understand-the-implementation"></a>
45
+ ## 理解实现
46
+
47
+ <details>
48
+ <summary>实现细节——点击展开</summary>
6
49
 
7
- 页头谱系 renderer 通过标准 `useSessions` 钩子读取 `subagentsByParent` 与会话摘要。普通会话保留当前 title,并在存在后代时,于页头操作行之前追加 `/` 和后代数量下拉菜单。每一级 subagent 面包屑都使用数量触发器的 12px 字号,并将 title 与固定显示的双向箭头组合成一个控件;当前面包屑保留主标签颜色和 500 字重,祖先面包屑则使用三级标签颜色和 400 字重。悬停组合控件 150ms 后会打开其直接 parent 目录,ArrowDown 保留为键盘入口;点击祖先面包屑会取消待打开的菜单并仅向上导航。每个目录都可切换 sibling,并会加粗选中行;目录 label 优先于会话摘要 title,尚未加载目录的每个切换器都会自动请求该目录。只有当前 subagent 会追加自己的后代数量下拉菜单。过长的面包屑 title 会截断,而双向箭头始终保留。数量触发器会统计仅含 subagent 的完整后代谱系,在普通 fork 处停止,并在任一计入统计的后代处于 `running` 时显示活动仍在进行。紧凑树仍以直接目录为权威依据:可继续和 one-shot 行会显示 mode、`running`/`inactive` 活动状态和由日志支撑的可选 title,尾随列则在上行显示提供方的持久化 token 用量总计,在下行显示活跃轮次耗时。token 用量总计为四个互不重叠的 `tokenUsage` 桶之和。视觉耗时在不足一天时精确到秒,达到一天后则最多使用两个相邻单位——天/小时、近似月份/天或近似年份/月份——而悬停信息与无障碍名称会保留精确的天/小时/分钟/秒数值。耗时会累加已完成的 `subagentTiming` 轮次,仅在运行中 child 存在未结束轮次时每秒递增一次,并在 child 变为 inactive 后冻结;被中断的未结束轮次以其同一切面的 `active.through` 为上界,绝不使用更新的会话元数据。没有 label 的 one-shot 行会回退到其会话 id,而损坏、不受支持或不可用的行仍保持可读但禁用。每个健康行的 `hasChildren` 提示会在交互前决定是否显示展开控件,因此已知叶子节点从不显示箭头;每层目录仅在其中至少一个健康行是分支时才预留展开列,使完全不含分支的层级能从最前面的状态标记开始。展开分支时,会立即为每个已知直接后代预留一行禁用的加载行,随后再用该 child 的权威目录懒加载结果替换这些占位行。每个可见分支都会上报给运行时,使成员帧只在树正被消费的位置触发去抖动刷新。选择任意深度的条目都会使用该行的确切地址 `{parentSessionId, childSessionId, mode}` 调用 `SessionRuntime.openSubagent()`。组件局部状态负责树的可见性、已展开分支、键盘焦点与运行中耗时时钟。ArrowRight/ArrowLeft 展开和折叠分支;ArrowUp/ArrowDown、Home、End 与 Escape 用于导航或关闭树;关闭后焦点返回触发器。样式只使用 token。
50
+ 目录与编辑器行为由 [Web subagent 对话笔记](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md) 与[当前轮次中断笔记](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md) 规定。
8
51
 
9
- one-shot child 始终选用只读编辑器,并将 transcript(文本记录)说明为已完成的执行记录。可继续 child 仅在其确切 parent 不可用且 child 未在运行时选用只读编辑器,并以文案说明恢复路径;此类 child 仍在运行期间,selector 会让位给普通编辑器——其输入区与 Send 操作被禁用,但独立的 Stop 保持可用,停止后只读替代恢复。确切 parent 存活时,可继续 child 保留普通输入 chrome,其会话通过 `subagent.prompt` 路由提示词:child 运行期间输入和 Send 保持可用,因为每条后续消息都会进入 child 的 FIFO inbox,而独立的 Stop 经由 `subagent.interrupt` 路由。本包绝不接收宿主上下文,也不调用面向模型的工具。目录与编辑器行为由 [Web subagent 对话 Agent Note](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md) 与[当前轮次中断 Agent Note](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md) 规定。
52
+ ### 目录派生
10
53
 
11
- 普通侧边栏会省略带 subagent origin 的会话行,因此 parent 页头目录是它们的导航入口。普通 fork 仍保留在侧边栏中。
54
+ 页头谱系 renderer 通过标准 `useSessions` 钩子读取 `subagentsByParent` 与会话摘要。紧凑树仍以直接目录为权威依据:每个健康行的 `hasChildren` 提示在交互前决定是否显示展开控件;每层目录仅在其中至少一个健康行是分支时才预留展开列;展开分支时会立即为每个已知直接后代预留一行禁用的加载行,随后再用该 child 的权威目录懒加载结果替换。每个可见分支都会上报给运行时,使成员帧只在树正被消费的位置触发去抖动刷新。
12
55
 
13
- `@` source 仍然刻意保持独立且惰性。候选是从 `ctx.sessions.list` 零 RPC 得到的运行中 child;pick 会插入字面文本 `@label `,codec 投影为 `@label`。它不参与命令裁决,也不会把 label 解析成继续执行地址。
56
+ ### 耗时与 token
14
57
 
58
+ token 用量总计为四个互不重叠的 `tokenUsage` 桶之和。耗时会累加已完成的 `subagentTiming` 轮次,仅在运行中 child 存在未结束轮次时每秒递增一次,并在 child 变为 inactive 后冻结;被中断的未结束轮次以其同一切面的 `active.through` 为上界,绝不使用更新的会话元数据。
59
+
60
+ ### 编辑器选举
61
+
62
+ one-shot child 始终选用只读编辑器。可继续 child 仅在其确切 parent 不可用且 child 未在运行时选用只读编辑器;否则普通编辑器的会话会经 `subagents/prompt` 路由提示词。本包绝不接收宿主上下文,也不调用面向模型的工具。
63
+
64
+ </details>
65
+
66
+ -----
67
+
68
+ <a id="further-exploration"></a>
69
+ ## 进一步探索
70
+
71
+ 以下页面覆盖对话界面、宿主 seam 与设计笔记。
72
+
73
+ - [ui-conversation](../ui-conversation/README.zh.md)——承载页头操作与编辑器链的聊天界面。
74
+ - [ui-input-trigger](../ui-input-trigger/README.zh.md)——承载 `@` source 的建议机制。
75
+ - [subagent](../../subagent/subagent/README.zh.md)——可继续 child 背后的宿主能力 seam。
76
+ - [Web subagent 对话](../../../.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md)——目录与编辑器规范。
77
+ - [当前轮次中断](../../../.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md)——独立 Stop 的语义。
78
+
79
+ -----
80
+
81
+ <a id="model-experience"></a>
15
82
  ## 模型体验
16
83
 
17
84
  ### 用户提示词中的 subagent label 文本
18
85
 
19
86
  #### 模型看到的内容
20
87
 
21
- 只有旧有 `@` 引用 source 会影响模型输入:pick 的候选以字面文本 `@label` 进入普通用户消息,没有专用内容块或宿主侧解析。浏览目录、导航 child 与查看持久化 transcript 都不会添加提示词 section;已接收的继续交互内容会经宿主 subagent 适配器成为普通 FIFO 用户消息。
88
+ 只有 `@` 引用 source 会影响模型输入:pick 的候选以字面文本 `@label` 进入普通用户消息,没有专用内容块或宿主侧解析。浏览目录、导航 child 与查看持久化 transcript 都不会添加提示词 section;已接收的继续交互内容会经宿主 subagent 适配器成为普通 FIFO 用户消息。
22
89
 
23
90
  #### Token 影响
24
91
 
@@ -28,7 +95,22 @@ one-shot child 始终选用只读编辑器,并将 transcript(文本记录)
28
95
 
29
96
  仅追加。本包绝不改写更早的请求 token。
30
97
 
31
- ## 已知限制与暂缓事项
98
+ ## 已知限制与延期工作
99
+
100
+ <a id="known-limitations-and-deferred-work"></a>
101
+
102
+
103
+ 这些限制定义目录能显示什么、`@` 引用意味着什么;它们是当前包约束。
32
104
 
33
105
  - **目录没有持久化结果**:活动状态与计时无法区分完成、失败或取消,且 UI 不公开 Activation 身份;停止能力仅限编辑器上针对运行中可继续 child 的当前轮次 Stop。
34
106
  - **`@` 引用仍是显示标题文本**:重复或改名后的 label 会有歧义,因此它们刻意不获得继续执行语义。
107
+
108
+ <a id="dev-note"></a>
109
+ ### 开发备注
110
+
111
+ <details>
112
+ <summary>维护者的工作上下文——点击展开</summary>
113
+
114
+ 无。
115
+
116
+ </details>
package/lib/client.js CHANGED
@@ -7,7 +7,6 @@ window.__ModuleLoader__.load({
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let react = require("react");
9
9
  let react_dom = require("react-dom");
10
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
11
10
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
12
11
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css.mjs
13
12
  const css$1 = ".ZKlsPq_root{align-items:center;gap:10px;min-width:0;display:inline-flex;position:relative}.ZKlsPq_switcherRoot{min-width:0;margin-left:6px}.ZKlsPq_trigger,.ZKlsPq_switcherTrigger{min-height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;align-items:center;padding:3px 2px;font-size:12px;line-height:18px;display:inline-flex}.ZKlsPq_trigger{gap:4px}.ZKlsPq_switcherTrigger{min-width:0;max-width:244px;color:var(--dsw-alias-label-primary);gap:4px;font-weight:500}.ZKlsPq_ancestorSwitcherTrigger{color:var(--dsw-alias-label-tertiary);font-weight:400}.ZKlsPq_switcherTitle{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.ZKlsPq_switcherTrigger svg{flex:none}.ZKlsPq_separator{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.ZKlsPq_activitySlot{flex:none;width:10px;height:10px;display:inline-flex}.ZKlsPq_trigger:hover,.ZKlsPq_trigger:focus-visible{color:var(--dsw-alias-label-secondary)}.ZKlsPq_switcherTrigger:hover,.ZKlsPq_switcherTrigger:focus-visible{color:var(--dsw-alias-label-primary)}.ZKlsPq_ancestorSwitcherTrigger:hover,.ZKlsPq_ancestorSwitcherTrigger:focus-visible{color:var(--dsw-alias-label-tertiary)}.ZKlsPq_trigger svg,.ZKlsPq_switcherTrigger svg{transition:transform .12s}.ZKlsPq_triggerOpen{transform:rotate(180deg)}.ZKlsPq_menu{z-index:100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);width:336px;max-width:min(400px,100vw - 32px);max-height:min(560px,100vh - 140px);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;padding:4px;display:flex;position:fixed;overflow:auto}.ZKlsPq_node{min-width:0;position:relative}.ZKlsPq_menu>.ZKlsPq_node{margin-left:-3px}.ZKlsPq_row{box-sizing:border-box;width:100%;min-height:50px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:8px;outline:none;align-items:flex-start;gap:8px;padding:7px 8px 7px 11px;font-size:13px;line-height:18px;display:flex;position:relative}.ZKlsPq_row:hover>.ZKlsPq_clickarea,.ZKlsPq_row:focus-visible>.ZKlsPq_clickarea{background:var(--dsw-alias-interactive-bg-hover)}.ZKlsPq_clickarea{box-sizing:border-box;border-radius:8px;flex:1;align-self:stretch;align-items:flex-start;gap:8px;min-width:0;margin:-7px -8px;padding:7px 8px;display:flex}.ZKlsPq_row>[data-state],.ZKlsPq_clickarea>[data-state]{margin-top:4px}.ZKlsPq_disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}.ZKlsPq_disabled:hover{background:0 0}.ZKlsPq_loadingRow{cursor:default}.ZKlsPq_disclosure,.ZKlsPq_disclosureSpace{flex:none;width:14px;height:18px}.ZKlsPq_disclosure{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;padding:0;transition:transform .12s;display:inline-flex}.ZKlsPq_disclosure:hover{color:var(--dsw-alias-label-primary)}.ZKlsPq_disclosureOpen{transform:rotate(90deg)}.ZKlsPq_content{flex-direction:column;flex:1;min-width:0;display:flex}.ZKlsPq_label,.ZKlsPq_summary{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.ZKlsPq_label{color:inherit;font-weight:400}.ZKlsPq_currentLabel{font-weight:600}.ZKlsPq_summary,.ZKlsPq_metrics{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}.ZKlsPq_metrics{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap;flex:none;grid-template-rows:18px 16px;display:grid}.ZKlsPq_metricToken{grid-row:1;line-height:18px}.ZKlsPq_metricDuration{grid-row:2}.ZKlsPq_children{margin-left:18px;padding-left:4px;position:relative}.ZKlsPq_children:before,.ZKlsPq_children>.ZKlsPq_node:before{content:\"\";border-left:1px solid var(--dsw-alias-border-l2);position:absolute;left:0}.ZKlsPq_children:before{height:26px;top:-26px}.ZKlsPq_children[aria-busy=true]:before{content:none}.ZKlsPq_children>.ZKlsPq_node:before{top:0;bottom:0;left:-4px}.ZKlsPq_children>.ZKlsPq_node:last-child:before{height:17px;bottom:auto}.ZKlsPq_children>.ZKlsPq_node>.ZKlsPq_row:before{content:\"\";border-top:1px solid var(--dsw-alias-border-l2);width:14px;position:absolute;top:16px;left:-4px}.ZKlsPq_notice,.ZKlsPq_error{color:var(--dsw-alias-label-tertiary);padding:10px 12px;font-size:12px;line-height:18px}.ZKlsPq_error{color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:center;gap:12px;display:flex}.ZKlsPq_refresh{color:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;align-items:center;gap:4px;padding:4px 6px;display:inline-flex}.ZKlsPq_refresh:hover{background:var(--dsw-alias-interactive-bg-hover)}";
@@ -51,6 +50,36 @@ window.__ModuleLoader__.load({
51
50
  "triggerOpen": "ZKlsPq_triggerOpen"
52
51
  };
53
52
  //#endregion
53
+ //#region lib/types/client/subagent-lineage.js
54
+ /** UI Subagent-owned projection of descendant counts from Session summaries. */
55
+ /**
56
+ * Index uninterrupted subagent descendants under each ancestor.
57
+ * @param summaries - Session summaries keyed by id.
58
+ * @returns descendant totals keyed by possible parent id.
59
+ */
60
+ function indexSubagentDescendants(summaries) {
61
+ const indexed = /* @__PURE__ */ new Map();
62
+ for (const descendant of Object.values(summaries)) {
63
+ if (descendant.origin !== "subagent") continue;
64
+ const seen = /* @__PURE__ */ new Set();
65
+ let current = descendant;
66
+ while (current?.origin === "subagent" && current.parentId !== void 0 && !seen.has(current.id)) {
67
+ seen.add(current.id);
68
+ const aggregate = indexed.get(current.parentId);
69
+ if (aggregate === void 0) indexed.set(current.parentId, {
70
+ count: 1,
71
+ runningCount: descendant.running ? 1 : 0
72
+ });
73
+ else {
74
+ aggregate.count += 1;
75
+ if (descendant.running) aggregate.runningCount += 1;
76
+ }
77
+ current = summaries[current.parentId];
78
+ }
79
+ }
80
+ return indexed;
81
+ }
82
+ //#endregion
54
83
  //#region lib/types/client/SubagentHeaderLineage.js
55
84
  function diagnosticReason(entry, t) {
56
85
  switch (entry.reason) {
@@ -63,11 +92,11 @@ window.__ModuleLoader__.load({
63
92
  return root === null ? [] : Array.from(root.querySelectorAll("[role=\"treeitem\"]:not([aria-disabled=\"true\"])"));
64
93
  }
65
94
  /** Compact token count shared in shape with the conversation stats strip. */
66
- function formatTokens(value) {
95
+ function formatTokens(value, t) {
67
96
  const scaled = (next) => next >= 100 ? String(Math.round(next)) : String(Math.round(next * 10) / 10);
68
97
  if (value < 1e3) return String(value);
69
- if (value < 1e6) return `${scaled(value / 1e3)}K`;
70
- return `${scaled(value / 1e6)}M`;
98
+ if (value < 1e6) return t("tokens.thousand", { value: scaled(value / 1e3) });
99
+ return t("tokens.million", { value: scaled(value / 1e6) });
71
100
  }
72
101
  /** Sum the four disjoint durable provider-usage buckets. */
73
102
  function tokenTotal(usage) {
@@ -257,7 +286,7 @@ window.__ModuleLoader__.load({
257
286
  ].filter((value) => value !== void 0).join(" · ");
258
287
  const totalTokens = tokenTotal(summary?.projectionValues?.tokenUsage);
259
288
  const durationMs = activityDuration(summary, entry.activity, now);
260
- const tokenMetric = totalTokens === void 0 ? void 0 : `${formatTokens(totalTokens)} tok`;
289
+ const tokenMetric = totalTokens === void 0 ? void 0 : t("tokens.total", { value: formatTokens(totalTokens, t) });
261
290
  const durationMetric = durationMs === void 0 ? void 0 : {
262
291
  compact: formatDuration(durationMs, t),
263
292
  exact: formatExactDuration(durationMs, t)
@@ -392,13 +421,12 @@ window.__ModuleLoader__.load({
392
421
  const hoverOpenTimer = (0, react.useRef)(void 0);
393
422
  const hoverCloseTimer = (0, react.useRef)(void 0);
394
423
  const observedCatalogs = (0, react.useRef)(/* @__PURE__ */ new Set());
395
- const requestedInitialCatalog = (0, react.useRef)();
396
424
  const setCatalogOpenRef = (0, react.useRef)(setCatalogOpen);
397
425
  setCatalogOpenRef.current = setCatalogOpen;
398
426
  const currentEntry = currentSessionId === void 0 ? void 0 : catalog?.entries.find((entry) => entry.kind === "child" && entry.id === currentSessionId);
399
427
  const switcherDisplayTitle = currentEntry?.kind === "child" ? currentEntry.label ?? currentEntry.id : displayTitle;
400
428
  const healthy = catalog?.entries.filter((entry) => entry.kind === "child") ?? [];
401
- const descendants = (0, react.useMemo)(() => (0, _deepseek_ai_dsh_client_runtime_client.indexSubagentDescendants)(summaries).get(rootSessionId) ?? NO_DESCENDANTS, [rootSessionId, summaries]);
429
+ const descendants = (0, react.useMemo)(() => indexSubagentDescendants(summaries).get(rootSessionId) ?? NO_DESCENDANTS, [rootSessionId, summaries]);
402
430
  const descendantCount = Math.max(healthy.length, descendants.count);
403
431
  const totalCountKey = descendantCount === 1 ? "count.total.one" : "count.total.other";
404
432
  const runningCountKey = descendants.runningCount === 1 ? "count.running.one" : "count.running.other";
@@ -408,16 +436,6 @@ window.__ModuleLoader__.load({
408
436
  state: "loading",
409
437
  error: null
410
438
  } : catalog;
411
- (0, react.useEffect)(() => {
412
- if (variant !== "switcher" || catalog !== void 0 || requestedInitialCatalog.current === rootSessionId) return;
413
- requestedInitialCatalog.current = rootSessionId;
414
- refresh(rootSessionId);
415
- }, [
416
- catalog,
417
- refresh,
418
- rootSessionId,
419
- variant
420
- ]);
421
439
  const observeCatalog = (parentSessionId, next) => {
422
440
  if (next) observedCatalogs.current.add(parentSessionId);
423
441
  else observedCatalogs.current.delete(parentSessionId);
@@ -725,6 +743,9 @@ window.__ModuleLoader__.load({
725
743
  "duration.yearsMonths": "约{years}年{months}个月",
726
744
  "duration.exactDays": "{days}天{hours}小时{minutes}分{seconds}秒",
727
745
  "duration.exactTitle": "总活跃耗时:{duration}",
746
+ "tokens.thousand": "{value}K",
747
+ "tokens.million": "{value}M",
748
+ "tokens.total": "{value} tok",
728
749
  "loading.label": "正在加载子代理…",
729
750
  "loading.aria": "正在加载子代理",
730
751
  "load.error": "无法加载子代理",
@@ -762,6 +783,9 @@ window.__ModuleLoader__.load({
762
783
  "duration.yearsMonths": "~{years}y {months}mo",
763
784
  "duration.exactDays": "{days}d {hours}h {minutes}m {seconds}s",
764
785
  "duration.exactTitle": "Total active duration: {duration}",
786
+ "tokens.thousand": "{value}K",
787
+ "tokens.million": "{value}M",
788
+ "tokens.total": "{value} tok",
765
789
  "loading.label": "Loading subagents…",
766
790
  "loading.aria": "Loading subagents",
767
791
  "load.error": "Unable to load subagents",
@@ -796,7 +820,7 @@ window.__ModuleLoader__.load({
796
820
  const subagent = owner.session?.subagent;
797
821
  if (subagent === void 0 || subagent === null) return null;
798
822
  if (subagent.address.mode === "one-shot") return { reason: "one-shot" };
799
- if (subagent.parentAvailable) return null;
823
+ if (subagent.parentAvailable !== false) return null;
800
824
  return owner.session?.running === true ? null : { reason: "parent-unavailable" };
801
825
  }
802
826
  /**
@@ -1,4 +1,5 @@
1
- import { type SessionId, type SubagentAddress } from '@deepseek-ai/dsh-client-runtime/client';
1
+ import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client';
2
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
2
3
  import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
3
4
  import { NS } from './locales.ts';
4
5
  /** Business actions supplied by the slot registration. */
@@ -1,5 +1,5 @@
1
1
  /** Web subagent catalog, navigation, and addressed-session composer owner. */
2
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
2
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
3
3
  import { type SubagentKey } from './locales.ts';
4
4
  declare module '@deepseek-ai/dsh-client-ui-slots' {
5
5
  interface LocaleNamespaceMap {
@@ -17,6 +17,9 @@ export declare const zh: {
17
17
  readonly 'duration.yearsMonths': "约{years}年{months}个月";
18
18
  readonly 'duration.exactDays': "{days}天{hours}小时{minutes}分{seconds}秒";
19
19
  readonly 'duration.exactTitle': "总活跃耗时:{duration}";
20
+ readonly 'tokens.thousand': "{value}K";
21
+ readonly 'tokens.million': "{value}M";
22
+ readonly 'tokens.total': "{value} tok";
20
23
  readonly 'loading.label': "正在加载子代理…";
21
24
  readonly 'loading.aria': "正在加载子代理";
22
25
  readonly 'load.error': "无法加载子代理";
@@ -0,0 +1,21 @@
1
+ /** UI Subagent-owned projection of descendant counts from Session summaries. */
2
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
3
+ interface LineageEntry {
4
+ readonly id: SessionId;
5
+ readonly parentId?: SessionId;
6
+ readonly origin?: 'subagent';
7
+ readonly running: boolean;
8
+ }
9
+ /** Descendant counts for one possible parent Session. */
10
+ export interface SubagentDescendantSummary {
11
+ readonly count: number;
12
+ readonly runningCount: number;
13
+ }
14
+ /**
15
+ * Index uninterrupted subagent descendants under each ancestor.
16
+ * @param summaries - Session summaries keyed by id.
17
+ * @returns descendant totals keyed by possible parent id.
18
+ */
19
+ export declare function indexSubagentDescendants(summaries: Readonly<Record<SessionId, LineageEntry>>): ReadonlyMap<SessionId, SubagentDescendantSummary>;
20
+ export {};
21
+ //# sourceMappingURL=subagent-lineage.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-subagent",
3
3
  "description": "Subagent conversation catalog, continuation routing UI, and '@' reference source",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -32,8 +32,8 @@
32
32
  "dsh": {
33
33
  "client": {
34
34
  "inject": [
35
+ "@deepseek-ai/dsh-api-session-controller",
35
36
  "@deepseek-ai/dsh-client-locale",
36
- "@deepseek-ai/dsh-client-runtime",
37
37
  "@deepseek-ai/dsh-client-ui-conversation",
38
38
  "@deepseek-ai/dsh-client-ui-primitives",
39
39
  "@deepseek-ai/dsh-client-ui-input-trigger"
@@ -43,31 +43,28 @@
43
43
  },
44
44
  "license": "MIT",
45
45
  "peerDependencies": {
46
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
47
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
48
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.1-rc.2",
49
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
50
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
51
- "@deepseek-ai/dsh-subagent": "^0.1.1-rc.2",
52
- "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
53
- "@deepseek-ai/cordis": "^4.0.1"
46
+ "@deepseek-ai/cordis": "^4.0.2"
54
47
  },
55
48
  "devDependencies": {
56
49
  "@types/react": "~18.3.1",
57
50
  "@types/react-dom": "~18.3.0",
58
51
  "react": "^18.2.0",
59
52
  "react-dom": "^18.2.0",
60
- "@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
61
- "@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
62
- "@deepseek-ai/dsh-client-test-runtime": "^0.1.1-rc.2",
63
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.1-rc.2",
64
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
65
- "@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
66
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
67
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
68
- "@deepseek-ai/dsh-subagent": "^0.1.1-rc.2",
69
- "@deepseek-ai/dsh-token-meter": "^0.1.1-rc.2",
70
- "@deepseek-ai/cordis": "^4.0.1"
53
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.3",
54
+ "@deepseek-ai/dsh-client-test-runtime": "^0.1.2-alpha.3",
55
+ "@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.3",
56
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.2-alpha.3",
57
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.3",
58
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.3",
59
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
60
+ "@deepseek-ai/dsh-subagent": "^0.1.2-alpha.3",
61
+ "@deepseek-ai/dsh-token-meter": "^0.1.2-alpha.3",
62
+ "@deepseek-ai/cordis": "^4.0.2",
63
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.3",
64
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.3",
65
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
66
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
67
+ "@deepseek-ai/dsh-client-ui-session": "^0.1.2-alpha.3"
71
68
  },
72
69
  "files": [
73
70
  "lib/index.js",