feat: optimize subagent list children query
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md
|
||||
2026-07-27-web-subagent-conversations.md: d75bc1471815cb0da78cf078585022ca668ebb81
|
||||
2026-07-27-web-subagent-conversations.zh.md: 5cd2999cb6e8ad68bfeccd0f685dc6e4471b6091
|
||||
2026-07-27-web-subagent-conversations.md: 947f5a33fbb6db19a1e88bacecb557ae6d7b80f0
|
||||
2026-07-27-web-subagent-conversations.zh.md: 407e8d626846c71dec17ff0901b7169bd886c314
|
||||
|
||||
@@ -32,14 +32,14 @@ The Figma [subagent list](https://www.figma.com/design/jRBBK7zBgcszdVWQ0Fh5J8/Ha
|
||||
| --- | --- |
|
||||
| The session header opens a compact child list. | The action shows every direct catalog entry in service order, including disabled diagnostics. |
|
||||
| Selecting a row reuses the conversation UI. | Addressed history never activates the child; only a continuable row with a live parent retains the ordinary composer. |
|
||||
| Nested agents expand progressively. | Each disclosure loads only that row's direct catalog and retains its own parent address. |
|
||||
| Nested agents expand progressively. | Each row carries a one-level `hasChildren` snapshot; disclosure still loads only that row's direct catalog and retains its own parent address. |
|
||||
| Rows show labels, state, and relative time without duplicating sidebar rows. | Mode and `running`/`inactive` activity are textual as well as visual; optional title and time come from summaries. `SessionHeader.origin` removes duplicate navigation rows but grants no capability. |
|
||||
|
||||
## Product contract
|
||||
|
||||
The header action count includes healthy `kind: 'child'` entries and excludes diagnostics. It is absent only after a complete empty response. The tree presents continuable and one-shot rows, falling back to the session id when an optional one-shot label is absent. Corrupt, unsupported, and unavailable candidates remain visible as disabled diagnostic rows.
|
||||
The header action count includes healthy `kind: 'child'` entries and excludes diagnostics. It is absent only after a complete empty response. Every healthy row carries a read-time `hasChildren` hint derived only from direct lineage headers with durable `origin: 'subagent'`; normal healthy and diagnostic subagent candidates carry that marker, while ordinary forks do not. This lookahead reads no descendant event log, and the descriptor-backed catalog loaded after disclosure remains authoritative. The UI omits disclosure for a known leaf before interaction; the hint does not promise that the child will remain a leaf. The tree presents continuable and one-shot rows, falling back to the session id when an optional one-shot label is absent. Corrupt, unsupported, and unavailable candidates remain visible as disabled diagnostic rows.
|
||||
|
||||
`running` means the logical child record is live in the session corpus; `inactive` means it exists only in persistence. The UI does not translate either value into success, failure, cancellation, completeness, or resumability. `host/session-status` updates known activity in place. Membership, labels, mode, and diagnostics still require a debounced `subagent.list` refresh while the affected branch is open. A prompt response remains delivery-time authority.
|
||||
`running` means the logical child record is live in the session corpus; `inactive` means it exists only in persistence. The UI does not translate either value into success, failure, cancellation, completeness, or resumability. `host/session-status` updates known activity in place. A `host/session-added` frame for a direct subagent immediately flips any loaded parent row to `hasChildren: true`; membership, labels, mode, diagnostics, and the authoritative snapshot still require a debounced `subagent.list` refresh while the affected branch is open. A prompt response remains delivery-time authority.
|
||||
|
||||
Selecting a row records its exact address before opening the resident client `Session`. History pagination, event folding, tool render intents, titles, breadcrumbs, and live mux reconciliation reuse the ordinary conversation machinery. The catalog is an ARIA tree with lazy ArrowRight/ArrowLeft disclosure, linear ArrowUp/ArrowDown navigation, Home/End, Escape, and focus restoration.
|
||||
|
||||
@@ -51,7 +51,7 @@ Agent-bound auxiliary controls are unavailable in addressed child views. In part
|
||||
|
||||
`@deepseek-ai/dsh-host-apiproxy` owns a browser-safe `subagents` domain:
|
||||
|
||||
- `subagent.list` takes `parentSessionId`, calls `ctx.subagents.listChildren(parentSessionId, signal)`, returns the complete ordered entries, and includes whether the exact parent currently resolves from `ctx.agents`.
|
||||
- `subagent.list` takes `parentSessionId`, calls `ctx.subagents.listChildren(parentSessionId, signal)`, returns the complete ordered entries with each healthy row's boolean `hasChildren` snapshot, and includes whether the exact parent currently resolves from `ctx.agents`.
|
||||
- `subagent.history` takes the full mode-bearing address plus ordinary page arguments. It verifies the child and mode against the direct catalog, reads through `ctx.sessionQuery.readSession()`, rechecks direct lineage, and returns the ordinary raw-event, render-intent, pagination, and host-computed session-projection baseline without publishing an Agent.
|
||||
- `subagent.prompt` accepts only a `mode: 'continuable'` address and `ContentBlock[]`. It requires the exact live parent, revalidates the catalog address, calls `ctx.subagents.followup(parent, childId, content, { source, signal })`, and returns the accepted `MessageId`.
|
||||
|
||||
@@ -69,7 +69,7 @@ The React-free runtime owns catalogs, single-flight refreshes, retained addresse
|
||||
|
||||
Catalogs ride the standard `useSessions` snapshot. Component-local state owns menu visibility, expanded branches, and focus. `ui-conversation` declares the generic header-action list slot and dispatches the current conversation snapshot through its composer chain; it contains no subagent-specific takeover flag. `@deepseek-ai/dsh-client-ui-subagent` registers the catalog action and elects a reason-specific read-only composer from ordinary owner props. Components receive derived props and callbacks, never `ctx`.
|
||||
|
||||
Every in-process subagent child stamps `SessionHeader.origin: 'subagent'` before publication. Session list summaries and incremental Host frames project it so grouped and flat sidebars omit duplicate child rows while preserving ordinary forks. Descriptor mode and catalog verification remain the authority for navigation, continuation, and authorization.
|
||||
Every in-process subagent child stamps `SessionHeader.origin: 'subagent'` before publication. Session list summaries and incremental Host frames project it so grouped and flat sidebars omit duplicate child rows while preserving ordinary forks. The same existing `host/session-added` frame marks a loaded direct parent row expandable without introducing a catalog event stream. Descriptor mode and catalog verification remain the authority for navigation, continuation, and authorization.
|
||||
|
||||
The package's existing `@label` source remains separate plain-text model input. It does not resolve labels to addresses or acquire continuation semantics.
|
||||
|
||||
@@ -93,23 +93,23 @@ The shipped Web composition mounts SQLite session query beside JSONL persistence
|
||||
|
||||
**Infer mode or sidebar filtering from lineage.** Rejected because ordinary forks share `parentSession`. The descriptor-backed catalog owns mode; the separate `origin` marker is only a cheap navigation classifier.
|
||||
|
||||
**Build an eager recursive tree or dedicated catalog stream.** Rejected for the current scale. Lazy direct-child reads preserve ordering and diagnostics; existing Host frames update activity and trigger bounded membership refreshes.
|
||||
**Build an eager recursive tree or dedicated catalog stream.** Rejected for the current scale. Header-only one-level expandability lookahead preserves pre-click stability without reading descendant events, while disclosure remains a lazy authoritative direct-child read; existing Host frames update activity, restore expandable parent rows, and trigger bounded membership refreshes.
|
||||
|
||||
**Let a child remain independently interactive after its parent disappears.** Rejected because independent lifetime and user ownership require side-session semantics.
|
||||
|
||||
## Testing
|
||||
|
||||
- Host protocol tests pin schemas, id echoing, mode verification, non-activating history, exact-parent enforcement, FIFO admission receipts, cancellation, and sanitized failure mapping.
|
||||
- Host protocol tests pin schemas including required boolean expandability, id echoing, mode verification, non-activating history, exact-parent enforcement, FIFO admission receipts, cancellation, and sanitized failure mapping.
|
||||
- Generic Host tests pin attached and cold history and forks without Agent publication, cold projection folding, descriptor/origin/runtime-owner denial, explicit-id adoption denial, and the direct queue-control fence.
|
||||
- Client object tests pin retained and restored addresses, one-shot read-only rejection, history routing, continuable prompt routing, no addressed cancellation, suppression of Agent-bound model controls, live activity flips, and membership refresh.
|
||||
- jsdom tests pin mixed-mode rows, diagnostics, lazy descendant disclosure, direct-parent addresses, keyboard behavior, and both read-only reasons.
|
||||
- Client object tests pin retained and restored addresses, one-shot read-only rejection, history routing, continuable prompt routing, no addressed cancellation, suppression of Agent-bound model controls, live activity flips, subagent-parent expandability flips, and membership refresh.
|
||||
- jsdom tests pin mixed-mode rows, pre-click leaf disclosure, diagnostics, lazy descendant disclosure, direct-parent addresses, keyboard behavior, and both read-only reasons.
|
||||
- The keyless assembled Web snapshot contains an inactive continuable child, an inactive one-shot sibling, and a persisted grandchild; it expands without activation, opens persisted history, admits a human FIFO follow-up, reconciles child mux events, and proves one-shot history remains read-only.
|
||||
- Sidebar tests pin `origin: 'subagent'` filtering without hiding ordinary forks.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Catalog reads may rescan persisted lineage and descriptor logs, so activity uses existing live frames while membership refresh stays debounced and single-flight.
|
||||
- Parent availability and child activity are process-local snapshots. Publication, disposal, another sender, or another process may win after listing; typed prompt failure remains expected.
|
||||
- Catalog reads may rescan persisted lineage and each direct candidate's descriptor log, but expandability reuses only descendant headers already present in that trace; activity uses existing live frames while membership refresh stays debounced and single-flight.
|
||||
- Parent availability, child activity, and `hasChildren` are snapshots. Publication, disposal, another sender, or another process may win after listing; typed prompt failure remains expected.
|
||||
- A child may publish between history fetch and mux subscription, so the existing sequence reconciliation also covers the cold-to-live addressed path.
|
||||
- Persisted origin adds one deliberately weak product-classification field to child headers and list projections; it cannot become an authorization shortcut.
|
||||
- The UI has no child cancellation, durable outcome, activation duration, deletion, or independently interactive offline mode, and its text must not imply those capabilities.
|
||||
|
||||
@@ -32,14 +32,14 @@ Figma 中的 [subagent 列表](https://www.figma.com/design/jRBBK7zBgcszdVWQ0Fh5
|
||||
| --- | --- |
|
||||
| 会话页头可打开紧凑的 child 列表。 | 该操作按服务顺序显示每个直接目录条目,包括已禁用的 diagnostic。 |
|
||||
| 选择一行会复用对话 UI。 | 已寻址历史绝不激活 child;只有 parent 存活的可继续行才保留普通输入框。 |
|
||||
| 嵌套 agent 会逐层展开。 | 每次展开只加载该行的直接目录,并保留其自身的 parent 地址。 |
|
||||
| 嵌套 agent 会逐层展开。 | 每行携带一层 `hasChildren` 快照;展开时仍只加载该行的直接目录,并保留其自身的 parent 地址。 |
|
||||
| 条目显示 label、状态与相对时间,同时避免侧边栏条目重复。 | mode 与 `running`/`inactive` 活动状态会同时以文字和视觉呈现;可选 title 与时间来自摘要。`SessionHeader.origin` 会移除重复的导航条目,但不授予任何功能权限。 |
|
||||
|
||||
## 产品契约
|
||||
|
||||
页头操作的计数包含健康的 `kind: 'child'` 条目,不包含 diagnostic。只有在完整响应为空后,才不显示该操作。树会呈现可继续与 one-shot 行;one-shot 的可选 label 缺失时,回退到其会话 id。损坏、不受支持或不可用的候选仍以禁用的 diagnostic 行显示。
|
||||
页头操作的计数包含健康的 `kind: 'child'` 条目,不包含 diagnostic。只有在完整响应为空后,才不显示该操作。每个健康行都携带读取时的 `hasChildren` 提示,该值只根据持久化 `origin: 'subagent'` 的直接谱系 header 派生;正常的健康与 diagnostic subagent 候选都会携带该标记,而普通 fork 不会。该预查不读取任何后代事件日志,展开后仍以描述符支撑的目录为权威依据。UI 会在交互前就省略已知叶子节点的展开控件;该提示不承诺 child 会一直是叶子。树会呈现可继续与 one-shot 行;one-shot 的可选 label 缺失时,回退到其会话 id。损坏、不受支持或不可用的候选仍以禁用的 diagnostic 行显示。
|
||||
|
||||
`running` 表示逻辑 child 记录存活于会话语料库中;`inactive` 表示它只存在于持久化存储中。UI 不会把任一值解释为成功、失败、取消、完成状态或可恢复性。`host/session-status` 会就地更新已知活动状态。受影响分支打开期间,成员、label、mode 与 diagnostic 仍需要通过去抖动的 `subagent.list` 刷新来更新。消息投递时仍以提示词响应为权威依据。
|
||||
`running` 表示逻辑 child 记录存活于会话语料库中;`inactive` 表示它只存在于持久化存储中。UI 不会把任一值解释为成功、失败、取消、完成状态或可恢复性。`host/session-status` 会就地更新已知活动状态。直接 subagent 的 `host/session-added` 帧会立即把任何已加载的 parent 行翻转为 `hasChildren: true`;受影响分支打开期间,成员、label、mode、diagnostic 与权威快照仍需要通过去抖动的 `subagent.list` 刷新来更新。消息投递时仍以提示词响应为权威依据。
|
||||
|
||||
选择一行后,系统会先记录其确切地址,再打开常驻客户端 `Session`。历史分页、事件 fold、工具渲染意图、title、面包屑导航与实时 mux 归并都会复用普通对话机制。目录是一棵 ARIA 树,支持懒加载式 ArrowRight/ArrowLeft 展开与折叠、线性 ArrowUp/ArrowDown 导航、Home/End、Escape 以及焦点恢复。
|
||||
|
||||
@@ -51,7 +51,7 @@ one-shot 行始终会用文案替代输入框,说明执行记录为只读。
|
||||
|
||||
`@deepseek-ai/dsh-host-apiproxy` 拥有浏览器安全的 `subagents` 域:
|
||||
|
||||
- `subagent.list` 接受 `parentSessionId`,调用 `ctx.subagents.listChildren(parentSessionId, signal)`,返回完整有序的条目,并说明当前能否从 `ctx.agents` 解析出确切 parent。
|
||||
- `subagent.list` 接受 `parentSessionId`,调用 `ctx.subagents.listChildren(parentSessionId, signal)`,返回完整有序的条目以及每个健康行的布尔 `hasChildren` 快照,并说明当前能否从 `ctx.agents` 解析出确切 parent。
|
||||
- `subagent.history` 接受包含 mode 的完整地址与普通页参数。它对照直接目录校验 child 与 mode,通过 `ctx.sessionQuery.readSession()` 读取,再次检查直接谱系,并在不发布 agent 的情况下返回普通原始事件、渲染意图、分页与由 Host 计算的会话投影基线。
|
||||
- `subagent.prompt` 只接受 `mode: 'continuable'` 地址与 `ContentBlock[]`。它要求确切的存活 parent,重新校验目录地址,调用 `ctx.subagents.followup(parent, childId, content, { source, signal })`,并返回已接受的 `MessageId`。
|
||||
|
||||
@@ -69,7 +69,7 @@ one-shot 行始终会用文案替代输入框,说明执行记录为只读。
|
||||
|
||||
目录通过标准 `useSessions` 快照传递。组件局部状态负责菜单可见性、已展开分支与焦点。`ui-conversation` 声明通用页头操作列表 slot,并通过其编辑器链分发当前对话快照;其中没有 subagent 专用的接管标记。`@deepseek-ai/dsh-client-ui-subagent` 注册目录操作,并根据普通 owner props 选择按原因区分的只读编辑器。组件只接收派生 props 与回调,绝不接收 `ctx`。
|
||||
|
||||
每个进程内 subagent child 都会在发布前写入 `SessionHeader.origin: 'subagent'`。会话列表摘要与增量 Host 帧会投影该字段,使分组和扁平侧边栏省略重复的 child 行,同时保留普通 fork。描述符 mode 与目录校验仍然是导航、继续执行和授权的权威依据。
|
||||
每个进程内 subagent child 都会在发布前写入 `SessionHeader.origin: 'subagent'`。会话列表摘要与增量 Host 帧会投影该字段,使分组和扁平侧边栏省略重复的 child 行,同时保留普通 fork。同一条现有的 `host/session-added` 帧还会把已加载的直接 parent 行标记为可展开,而无需引入目录事件流。描述符 mode 与目录校验仍然是导航、继续执行和授权的权威依据。
|
||||
|
||||
该包现有的 `@label` source 仍然是独立的面向模型纯文本输入。它不会将 label 解析为地址,也不会获得继续执行语义。
|
||||
|
||||
@@ -93,23 +93,23 @@ one-shot 行始终会用文案替代输入框,说明执行记录为只读。
|
||||
|
||||
**根据谱系推断 mode 或侧边栏过滤。** 不予采纳,因为普通 fork 共享 `parentSession`。由描述符支撑的目录负责提供 mode;单独的 `origin` 标记只是低成本的导航分类器。
|
||||
|
||||
**构建预先加载的递归树或专用目录流。** 就当前规模而言不予采纳。懒加载式直接 child 读取会保留排序与 diagnostic;现有 Host 帧会更新活动状态,并触发有界的成员刷新。
|
||||
**构建预先加载的递归树或专用目录流。** 就当前规模而言不予采纳。只读 header 的一层可展开性预查会在不读取后代事件的情况下保证点击前的稳定性,而展开仍是懒加载式权威直接 child 读取;现有 Host 帧会更新活动状态、恢复 parent 行的可展开性,并触发有界的成员刷新。
|
||||
|
||||
**让 child 在 parent 消失后仍能独立交互。** 不予采纳,因为独立生命周期与用户所有权需要 side session 语义。
|
||||
|
||||
## 测试
|
||||
|
||||
- 宿主协议测试固定 schema、id 回显、mode 校验、非激活式历史、确切 parent 强制要求、FIFO 准入回执、取消与脱敏后的失败映射。
|
||||
- 宿主协议测试固定 schema(包括必需的布尔可展开性)、id 回显、mode 校验、非激活式历史、确切 parent 强制要求、FIFO 准入回执、取消与脱敏后的失败映射。
|
||||
- 通用 Host 测试固定在不发布 Agent 的情况下读取已附加与冷态历史及执行 fork、冷态投影归并、按描述符/origin/运行时 owner 拒绝、拒绝显式 id 接纳,以及直接队列控制栅栏。
|
||||
- 客户端对象测试固定已保留与已恢复的地址、one-shot 只读拒绝、历史路由、可继续提示词路由、已寻址对话不提供取消、屏蔽绑定到 agent 的模型控件、实时活动状态翻转与成员刷新。
|
||||
- jsdom 测试固定混合 mode 行、diagnostic、后代懒加载展开、直接 parent 地址、键盘行为与两种只读原因。
|
||||
- 客户端对象测试固定已保留与已恢复的地址、one-shot 只读拒绝、历史路由、可继续提示词路由、已寻址对话不提供取消、屏蔽绑定到 agent 的模型控件、实时活动状态翻转、subagent parent 可展开性翻转与成员刷新。
|
||||
- jsdom 测试固定混合 mode 行、点击前的叶子展开控件、diagnostic、后代懒加载展开、直接 parent 地址、键盘行为与两种只读原因。
|
||||
- 无密钥的组装 Web 快照包含一个 inactive 的可继续 child、一个 inactive 的 one-shot sibling 和一个持久化 grandchild;它会在不激活的情况下展开、打开持久化历史、准入一条用户 FIFO 后续消息、归并 child mux 事件,并证明 one-shot 历史仍然只读。
|
||||
- 侧边栏测试固定 `origin: 'subagent'` 过滤,同时不隐藏普通 fork。
|
||||
|
||||
## 后果
|
||||
|
||||
- 目录读取可能重新扫描持久化谱系与描述符日志,因此活动状态使用现有实时帧,而成员刷新保持去抖动和单次并发。
|
||||
- parent 可用性与 child 活动状态都是进程局部快照。列出之后,发布、dispose、其他发送方或其他进程都可能抢先改变状态;类型化提示词失败仍属预期行为。
|
||||
- 目录读取可能重新扫描持久化谱系与每个直接候选的描述符日志,但可展开性只复用该追踪中已有的后代 header;因此活动状态使用现有实时帧,而成员刷新保持去抖动和单次并发。
|
||||
- parent 可用性、child 活动状态与 `hasChildren` 都是快照。列出之后,发布、dispose、其他发送方或其他进程都可能抢先改变状态;类型化提示词失败仍属预期行为。
|
||||
- child 可能在历史获取与 mux 订阅之间发布,因此现有序号归并也涵盖从冷态转为存活的已寻址路径。
|
||||
- 持久化 origin 会为 child header 与列表投影添加一个有意保持弱约束的产品分类字段;它不能变成授权捷径。
|
||||
- UI 不提供 child 取消、持久化结果、激活耗时、删除或可独立交互的离线 mode,其文案不得暗示这些功能已经存在。
|
||||
|
||||
Reference in New Issue
Block a user