dsh-plugin-dev-kb 1.1.0 → 1.3.0

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.en.md +14 -8
  3. package/README.md +14 -8
  4. package/docs/banner.svg +25 -0
  5. package/kb/INDEX.md +6 -2
  6. package/kb/README.md +4 -4
  7. package/kb/extra/AGENTS.md +2 -2
  8. package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
  9. package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
  11. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
  12. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
  14. package/kb/extra/deepseek-llm-api-wire-extensions.md +2 -2
  15. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +2 -2
  16. package/kb/extra/development.md +13 -3
  17. package/kb/extra/development.zh.md +13 -3
  18. package/kb/extra/event-producer-consumer.md +33 -31
  19. package/kb/extra/event-producer-consumer.zh.md +34 -32
  20. package/kb/extra/i18n/README.md +2 -1
  21. package/kb/extra/i18n/README.zh.md +3 -2
  22. package/kb/extra/i18n/translation-prompt.md +1 -1
  23. package/kb/extra/module-graph.md +62 -24
  24. package/kb/extra/module-graph.zh.md +62 -24
  25. package/kb/extra/rescope.md +1 -1
  26. package/kb/extra/rescope.zh.md +1 -1
  27. package/kb/extra/session-format-status.md +47 -0
  28. package/kb/extra/session-format-status.zh.md +47 -0
  29. package/kb/extra/subsystems/agent-team.md +1 -1
  30. package/kb/extra/subsystems/agent-team.zh.md +1 -1
  31. package/kb/extra/subsystems/feedback.md +164 -30
  32. package/kb/extra/subsystems/feedback.zh.md +164 -30
  33. package/kb/extra/testing.md +4 -3
  34. package/kb/extra/testing.zh.md +4 -3
  35. package/kb/extra/web-styling.md +1 -0
  36. package/kb/extra/web-styling.zh.md +1 -0
  37. package/kb/meta/search-index.json +151 -103
  38. package/kb/meta/site-pages.txt +188 -184
  39. package/kb/meta/source.json +5 -5
  40. package/kb/meta/topics.md +2 -2
  41. package/kb/site/develop/basic/publish.md +1 -1
  42. package/kb/site/en/develop/basic/publish.md +1 -1
  43. package/kb/site/en/guide/providers.md +1 -1
  44. package/kb/site/en/guide/python-sdk.md +19 -3
  45. package/kb/site/en/reference/agent-lifecycle.md +12 -5
  46. package/kb/site/en/reference/capability-seams.md +10 -3
  47. package/kb/site/en/reference/config-catalog.md +173 -42
  48. package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
  49. package/kb/site/en/reference/cookbook/extension-cookbook.md +1 -1
  50. package/kb/site/en/reference/index.md +18 -8
  51. package/kb/site/en/reference/persistence-catalog.md +121 -48
  52. package/kb/site/en/reference/subsystems/approval.md +1 -1
  53. package/kb/site/en/reference/subsystems/client-modules.md +9 -0
  54. package/kb/site/en/reference/subsystems/client-resources.md +94 -0
  55. package/kb/site/en/reference/subsystems/compaction.md +1 -1
  56. package/kb/site/en/reference/subsystems/core.md +79 -16
  57. package/kb/site/en/reference/subsystems/filesystem.md +15 -0
  58. package/kb/site/en/reference/subsystems/goal.md +37 -1
  59. package/kb/site/en/reference/subsystems/index.md +2 -0
  60. package/kb/site/en/reference/subsystems/invariants.md +1 -1
  61. package/kb/site/en/reference/subsystems/llm-streaming.md +20 -10
  62. package/kb/site/en/reference/subsystems/lsp.md +1 -1
  63. package/kb/site/en/reference/subsystems/persistence.md +36 -15
  64. package/kb/site/en/reference/subsystems/schedule.md +1 -1
  65. package/kb/site/en/reference/subsystems/scope.md +1 -1
  66. package/kb/site/en/reference/subsystems/session-reference.md +4 -0
  67. package/kb/site/en/reference/subsystems/session-telemetry.md +22 -6
  68. package/kb/site/en/reference/subsystems/session.md +64 -42
  69. package/kb/site/en/reference/subsystems/shell.md +9 -6
  70. package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
  71. package/kb/site/en/reference/subsystems/slots.md +41 -33
  72. package/kb/site/en/reference/subsystems/spill.md +13 -6
  73. package/kb/site/en/reference/subsystems/subagent.md +20 -13
  74. package/kb/site/en/reference/subsystems/subprocess.md +25 -26
  75. package/kb/site/en/reference/subsystems/system-prompt.md +3 -1
  76. package/kb/site/en/reference/subsystems/token-meter.md +2 -1
  77. package/kb/site/en/reference/subsystems/tools.md +5 -5
  78. package/kb/site/en/reference/subsystems/workspace.md +80 -1
  79. package/kb/site/en/reference/tool-catalog.md +46 -2
  80. package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
  81. package/kb/site/guide/providers.md +1 -1
  82. package/kb/site/guide/python-sdk.md +19 -3
  83. package/kb/site/reference/agent-lifecycle.md +12 -5
  84. package/kb/site/reference/capability-seams.md +10 -3
  85. package/kb/site/reference/config-catalog.md +173 -42
  86. package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
  87. package/kb/site/reference/cookbook/extension-cookbook.md +1 -1
  88. package/kb/site/reference/index.md +18 -8
  89. package/kb/site/reference/persistence-catalog.md +127 -54
  90. package/kb/site/reference/subsystems/approval.md +1 -1
  91. package/kb/site/reference/subsystems/client-modules.md +9 -0
  92. package/kb/site/reference/subsystems/client-resources.md +94 -0
  93. package/kb/site/reference/subsystems/compaction.md +1 -1
  94. package/kb/site/reference/subsystems/core.md +79 -16
  95. package/kb/site/reference/subsystems/filesystem.md +15 -0
  96. package/kb/site/reference/subsystems/goal.md +37 -1
  97. package/kb/site/reference/subsystems/index.md +14 -12
  98. package/kb/site/reference/subsystems/invariants.md +1 -1
  99. package/kb/site/reference/subsystems/llm-streaming.md +20 -10
  100. package/kb/site/reference/subsystems/lsp.md +1 -1
  101. package/kb/site/reference/subsystems/persistence.md +36 -15
  102. package/kb/site/reference/subsystems/schedule.md +1 -1
  103. package/kb/site/reference/subsystems/scope.md +1 -1
  104. package/kb/site/reference/subsystems/session-reference.md +4 -0
  105. package/kb/site/reference/subsystems/session-telemetry.md +22 -6
  106. package/kb/site/reference/subsystems/session.md +64 -42
  107. package/kb/site/reference/subsystems/shell.md +9 -6
  108. package/kb/site/reference/subsystems/sidebar-right.md +151 -0
  109. package/kb/site/reference/subsystems/slots.md +41 -33
  110. package/kb/site/reference/subsystems/spill.md +13 -6
  111. package/kb/site/reference/subsystems/subagent.md +20 -13
  112. package/kb/site/reference/subsystems/subprocess.md +25 -26
  113. package/kb/site/reference/subsystems/system-prompt.md +3 -1
  114. package/kb/site/reference/subsystems/token-meter.md +2 -1
  115. package/kb/site/reference/subsystems/tools.md +5 -5
  116. package/kb/site/reference/subsystems/workspace.md +80 -1
  117. package/kb/site/reference/tool-catalog.md +46 -2
  118. package/kb/site/reference/tool-execution-pipeline.md +2 -2
  119. package/lib/index.js +25 -44
  120. package/package.json +3 -2
  121. package/skills/dsh-plugin-dev-kb.md +3 -3
@@ -0,0 +1,151 @@
1
+ ---
2
+ editSource: "docs/subsystems/sidebar-right.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # 右侧 Sidebar
7
+
8
+ 右侧 Sidebar 是 Web Client 里每个会话一份的停靠面:会话区旁的一列 pane 与 tab,按地址寻址的内容——工作区文件、目录树、产品自带页面——在这里打开、分栏、浮出、关闭。[`dsh-client-ui-sidebar-right`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md) 拥有这个面、tab 类型注册表与导航服务;[`dsh-client-ui-dockkit`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-dockkit/README.zh.md) 是它内部的布局引擎;[`dsh-client-resources`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md) 把地址变成任何组件都能读的活数据;[`dsh-api-workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) 同时提供 Host 工作区文件服务与 Client `file` 资源提供者。
9
+
10
+ 本页是该子系统契约的参考:地址、tab 类型注册、导航服务、扩展 slot 与其 owner props、资源模型、Workspace Files 服务、内置类型,以及明确不做的事。布局引擎、frame 与停靠面如何拼在一起见 [Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md);slot 机制见 [Slots 参考](./slots.md)。
11
+
12
+ ## 定位与归属
13
+
14
+ 每个会话恰有一个停靠面,保存在会话作用域的 slot store 里、由 `rightbar.session` 绘制。root 作用域的 `rightbar` 控制器仅在选中 Conversation 时挂载该席位;刷新页面后每个会话回到折叠的默认态,切换会话时各自的面保持原状([状态](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#state))。面的每一次变化都是 kit 纯规划器算出的一条历史记录;停靠的 pane 从不空着,根 pane 为空时会加入根据已注册引导入口选出的默认页。
15
+
16
+ 一个 tab 类型是共用定义 `id` 的两次注册:在 `ctx.sidebarRightTabs` 里的静态定义说明其 `kind` 打开哪些地址,一次 keyed slot 注册提供它的正文。框架注入 `useTabInfo()` 以读取 Sidebar、窗格和标签的实时信息;各类型把自身状态放在 slot store 里。各包之间只以类型形式引用彼此的声明。
17
+
18
+ | 包 | 职责 |
19
+ |---|---|
20
+ | [`client/ui-sidebar-right`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md) | 面板与栏席位、布局 store、`ctx.sidebarRightTabs`、`ctx.sidebarRight`、Tab 域、引导类型 |
21
+ | [`client/ui-dockkit`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-dockkit/README.zh.md) | 纯布局引擎与 React 面;`ui-sidebar-right` 的内部依赖,不是稳定接口 |
22
+ | [`client/resources`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md) | `ctx.resources`、`useResource`、协议 → 值类型的花名册 `ResourceProtocolMap` |
23
+ | [`api/workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) | Host `ctx.workspaceFiles`、`workspaceFiles` Remote 命名空间与 Client `file` 资源提供者 |
24
+ | [`util/workspace-path`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/util/workspace-path/README.zh.md) | 文件地址语法:`fileAddressFor`、`parseFileAddress` |
25
+ | [`client/ui-sidebar-documentpreview`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/README.zh.md)、[`client/ui-sidebar-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-files/README.zh.md) | 内置的 `text` 与 `files` 类型 |
26
+
27
+ ## 地址
28
+
29
+ 每个 tab 都由一个地址字串打开,地址就是 tab 的内容身份。地址分两族。
30
+
31
+ **资源地址**是 `dsh-resource://<type>/…` 形式的 URL。host 命名资源协议——即 `ResourceProtocolMap` 的键——其后是该协议自己的路径;所有协议共用一个 scheme,新增协议只新增 host、不新增 scheme。`file` 协议的路径以其作用域开头:`session/<sessionId>` 后接相对该会话工作区根的路径(`dsh-resource://file/session/abc/src/notes.txt`),或 `absolute` 后接去掉前导 `/` 的绝对路径(`dsh-resource://file/absolute/home/ys/notes.txt`,Windows 上为 `dsh-resource://file/absolute/C:/x/y.txt`)。id 与每一段路径都做组件编码,盘符的 `:` 保留原样。`fileAddressFor(sessionId, cwd, path)` 构造地址——相对路径或工作区内的绝对路径成为 `session` 相对地址,其他绝对路径成为 `absolute` 地址——`parseFileAddress(address)` 读回各部分或返回 `undefined`([语法](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/util/workspace-path/README.zh.md))。
32
+
33
+ **页面地址**是 Sidebar 为按 kind(而非按资源)打开的 tab 记下的地址:`sidebar://<kind>`,由 Sidebar 自己在 `openTab(kind)` 运行时写入。调用方从不拼它——引导页与文件树以 `openTab('guide')`、`openTab('files')` 打开——此外不存在任何导航地址([不做](#not-built))。
34
+
35
+ tab 身份是 `(kind, address)` 二元组:注册表的认领把地址原文用作记录的 `contentId`,因此同一地址经同一类型再次打开会找到已有 tab,同一地址经两个类型打开则是两个 tab。
36
+
37
+ ## Tab 类型注册
38
+
39
+ `ctx.sidebarRightTabs.register(definition)` 在调用方的生命周期内注册一个类型的一份实现并返回注销器;调用方把它放在自己的 `ctx.effect` 里,因此实现与贡献它的插件同寿,同一 `id` 的第二次注册抛错([扩展席位](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#extension-seats))。定义是静态的:没有运行时 hook,没有按 tab 或按会话的东西。
40
+
41
+ | 字段 | 含义 |
42
+ |---|---|
43
+ | `id` | 该实现的身份,在所有注册中唯一;包名是自然取值(`@deepseek-ai/dsh-client-ui-sidebar-files`)。正文与标题坑位按它注册。 |
44
+ | `kind` | 类型的判别名:它的 tab 是什么,也是 `openTab` 点名的对象。不唯一——extension 可以接管 builtin 的 kind。内置 kind 为 `guide`、`text`、`files`。 |
45
+ | `patterns` | 可选的资源地址 glob;按 kind 打开的页面类型省略。含 `:` 的模式匹配整个地址(`dsh-resource://file/**`);不含的匹配 URL 的路径部分且任意深度都中(`*.md`),不是 URL 的地址不会命中此类模式。匹配不分大小写、不隐藏 dotfile;语法为 picomatch 的 POSIX 方言。 |
46
+ | `priority` | 三档字面量之一:`extension`(缺省且最高:产品之外的类型压过所有内置查看器)、`builtin`(随产品发布的类型)、`fallback`(任何更具体的类型都应压过的纯内容查看器)。 |
47
+ | `canOpen(address)` | 可选的同步否决,对 glob 命中生效;每次路由决策都会调用。 |
48
+ | `title(address)` | chip 文本,在 tab 打开时捕获进布局记录,之后不再改写。 |
49
+ | `guide` | 可选的引导页入口框:`{ order, title(), description?(), icon? }`。点一框即把贡献它的类型作为页面打开;省略即不上引导页。 |
50
+
51
+ 路由是一次排序认领。`candidates(address)` 对模式命中且未被 `canOpen` 否决的类型排序:先按档,再按最长命中模式的长度,最后按注册顺序。`claim(address, kind?)` 取第一个候选,或直接用点名的 `kind`——跳过它的 glob,但 `canOpen` 仍生效——返回 `{ kind, contentId: address, title }`。没有任何类型认领的地址会抛错:这是接线错误,不是用户错误。
52
+
53
+ 同一个 `kind` 可同时携带一个 `builtin` 与一个 `extension` 注册。extension 在认领、`get(kind)`、`openTab(kind)` 与引导页上生效,席位按生效定义的 `id` 找 tab 的正文与标题,不涉及任何 slot 优先级;extension 注销后 builtin 恢复。kind 上的其它任何撞名以及任何重复的 `id` 都抛错。
54
+
55
+ ```ts ignore-check
56
+ import type { Context } from '@deepseek-ai/cordis'
57
+ import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
58
+
59
+ export const inject = ['sidebarRightTabs', 'slots']
60
+
61
+ export function apply(ctx: Context): void {
62
+ ctx.effect(() => ctx.sidebarRightTabs.register({
63
+ id: '@acme/dsh-client-ui-image',
64
+ kind: 'image',
65
+ patterns: ['*.png', '*.jpg', '*.gif', '*.svg'],
66
+ canOpen: address => address.startsWith('dsh-resource://file/'),
67
+ title: address => address.slice(address.lastIndexOf('/') + 1),
68
+ }), 'image type')
69
+ ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register(
70
+ { name: 'sidebar.right.pane.tab', key: '@acme/dsh-client-ui-image' },
71
+ ImageBody,
72
+ )), 'image body')
73
+ }
74
+ ```
75
+
76
+ ## 导航:`ctx.sidebarRight`
77
+
78
+ 两种打开构成导航控制器,进入这一列的每条路都调用其一:`openResource(address, options?)` 打开 `dsh-resource://` 地址——会话区的文件链接、工具行的行号引用、文件树的行;`openTab(kind, options?)` 打开页面——tab 条的新增控件、引导页入口框。两者都以一条历史记录走完四步——认领(注册表为资源排候选,或点名 `kind` 的生效实现应答);聚焦已显示同一 `(kind, address)` 的 tab;否则落一个新 tab;展开这一列——然后把导航记入 Tab 域([服务](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#ctxsidebarright))。用户看不见的内容不算打开,所以折叠的列会在同一步展开。`openResource` 对 `dsh-resource://` 之外的地址或无人认领的地址抛错;`openTab` 对无人注册的 kind 抛错:二者都是接线错误,不是用户错误。
79
+
80
+ | 选项 | 含义 |
81
+ |---|---|
82
+ | `paneId` | 新 tab 落到这个 pane;缺省为活动的停靠 pane(活动的是浮窗时取第一个停靠 pane)。 |
83
+ | `replaceTab` | 占用这个 tab 的 pane 与条上位置,并在同一步关闭它;浮窗里的 tab 让不出位置,新 tab 按未指定位置落位。 |
84
+ | `revealIfOpened` | 缺省 `true`:已显示同一 `(kind, address)` 的 tab 被聚焦并收到 `params`。`false` 则无论如何再开一个。 |
85
+ | `kind`(仅 `openResource`) | 点名打开类型而不排候选;该 kind 的生效实现打开地址,它的 `canOpen` 仍生效。 |
86
+ | `params` | 给正文的导航参数,作为 `navigation.params` 送达。`openResource` 按资源类型经声明合并表 `SidebarRightResourceParamsMap` 定型(文本预览声明 `{ line?: number }`);`openTab<K>` 按 kind 经 `SidebarRightTabParamsMap` 定型,未声明的 kind 为 `undefined`;正文读到的是二者联合 `SidebarRightNavigationParams`。值按约定为 JSON 形状,运行时不校验。 |
87
+
88
+ 落位是调用方的选项,从不是类型的属性。会话区调 `openResource(fileAddressFor(sessionId, cwd, path))`,`read` 工具行另加 `{ params: { line } }`(来自调用的 1 起 `offset`);引导页入口框调 `tab.actions.openTab(entry.kind, { replaceTab: true })`;文件树的行调 `tab.actions.openResource(address)`;tab 条的新增控件调 `openTab('guide', { paneId, revealIfOpened: false })`。
89
+
90
+ `close(tabId)` 关闭一个 tab;`active()` 返回活动 pane 的活动 tab;`isExpanded()` 与 `toggleExpanded()` 读取与翻转这一列,翻转记入序列。无会话时读操作返回 `undefined` 或 `false`;写操作需要已挂载的会话面,没有时抛错而不是写进没人绘制的面。
91
+
92
+ `focus(tabId)` 让一个 tab 成为其 pane 的活动 tab;`split(paneId?)` 分割活动的停靠 pane 或点名的 pane,返回新 pane 的 id——pane 数预算或列宽不允许时返回 `undefined` 且不记账;`float(tabId, rect?)` 把一个 tab 浮出为浮窗 pane;`dock(paneId)` 把浮窗 pane 收回停靠区。四者都走 store 既有动作、各记一条历史;目标不存在或已处于目标状态时是空操作,与 `open` 一样在没有已挂载会话面时抛错。`TabId`、`PaneId`、`TabRecord`、`FloatRect` 自本包 `/client` 入口再导出,调用方无需引 dockkit。
93
+
94
+ ## Slot 与 owner props
95
+
96
+ Sidebar 声明四个扩展 slot;其文档 tab 另行声明下表中的 keyed 文档正文 slot([层级](./slots.md))。
97
+
98
+ | Slot | Cardinality | 用途 |
99
+ |---|---|---|
100
+ | `sidebar.right.pane.tab` | 按定义的 `id` keyed,会话作用域 | 一个 tab 的正文。席位把 tab 分发到其 kind 生效实现的 `id`,因此注册者收到该 kind 的每个 tab,停靠或浮窗。实现没有注册正文的 kind 渲染 owner 的「无法查看此内容」提示。 |
101
+ | `sidebar.right.pane.tab.title` | 按定义的 `id` keyed,会话作用域 | chip 的标题,owner share 与正文相同。可选:没有条目时 chip 显示打开时捕获的 `title(address)` 文本;有活标题的类型在此读自己的 store。 |
102
+ | `sidebar.right.tab.guide` | chain,会话作用域 | 替换引导 tab 的内容而不替换 tab;第一个不拒绝的条目接管正文,否则渲染自带引导。 |
103
+ | `sidebar.right.tab.menu.item` | list,会话作用域 | 追加在 kit 自身布局动作之后的内容级动作。执行了动作的条目必须调用 owner 的 `dismiss()`。 |
104
+ | `sidebar.right.tab.document` | 按文档实现的 `id` keyed,会话作用域 | 文档 tab 内选中的文件渲染器;父组件拥有共享加载与工具栏控件。 |
105
+
106
+ 正文、标题与引导页替换项接收框架注入的 `useTabInfo()`。它返回 `{ sidebar, panel, tab }`:`sidebar` 包含 `expanded` 与 `fullscreen`,`panel.id` 标识所属窗格,`tab` 包含记录字段以及 `visible`、`navigation`、`signal` 和 `actions`。停靠正文仅在展开且活跃时可见;停靠标题只要求展开;浮窗保持可见。`signal` 在记录消失或插件卸载时中止,不因隐藏或切换 Session 而中止。`tab.actions` 提供绑定到标签所属 Session 的 `openResource`、`openTab` 与 `close`。打开位置缺省为当前所属窗格;`revealIfOpened` 缺省为 `true`,`replaceTab: true` 在同一历史项中替换本记录。菜单项保留普通的 `tab` 与 `dismiss` owner 参数。
107
+
108
+ `navigation.revision` 在每次导航到该 tab 时递增,`params` 不变也递增,正文可仅凭「又被导航了」行动;按地址打开的 tab 为 `1`,没有人按地址打开的记录——种入的引导、撤销恢复的 tab——为 `0`。Tab 域为每条打开的记录保有一个 occurrence:记录出现即在资源模型里钉住,因此切换 tab 卸载正文也不丢内容;记录消失即中止并丢弃;撤销恢复的记录是新的 occurrence([Tab 域](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#the-tab-domain))。
109
+
110
+ ## 文档渲染器
111
+
112
+ `text` tab 是共享的 Document Preview 所有者。其[根注册](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/index.ts)声明 `sidebar.right.tab.document` 并提供 `ctx.documentPreviews`。渲染器在自己的 effect 中注册 `DocumentPreviewDefinition` 元数据,再通过 `ctx.slots.inject('sidebar.right.tab.document', ...)` 等待 slot,以 `key: definition.id` 和自己的 locale 命名空间注册组件。切换渲染器不改变 tab 或资源地址;[扩展决议](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md)将预览策略与资源归属分开。
113
+
114
+ [注册表](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/document/registry.ts)记录唯一的 `id`、`extensions`、本地化 `title()`、`loading`,以及可选的 `priority` 和 `wrap`。后缀匹配不区分大小写,先排 `extension`(缺省值)、再排 `builtin`,随后比较后缀长度(长者优先)与注册顺序。与 tab kind 替换不同,注册表保留所有实现;工具栏列出匹配的候选,按 tab 记住选择。未知扩展名使用纯文本。`loading` 为 `text-pages` 或 `bytes-complete`;`wrap` 声明是否支持共享的源码换行控件。
115
+
116
+ [`DocumentPreviewProps`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/document/contract.ts) 派生自 `PropsRuntime<'sidebar.right.tab.document'>`。owner 提供原始 `resourceAddress`、`content` 与当前 `wrap`:文本内容为 `{ kind: 'text', text, pages: [{ offset, text, lines }], eof }`,其中 `text` 为累积文本;完整字节为 `{ kind: 'bytes', data }`,其中 `data` 为 `Uint8Array<ArrayBuffer>`。这些瞬时缓冲区按只读方式借用,不得进入持久布局或 Session JSON。PDF 在转移到 Worker 前复制字节,以保留 owner 的缓冲区。子组件收到同一个框架绑定的 `useTabInfo`,以及全局共享、仅提供元数据的 `useResource`。父组件通过普通 inject 回调调用 `remote.workspaceFiles.read`/`readAll`,拥有追加分页、逐 tab 刷新与加载状态。HTML 自己的 inject 回调使用 `readRelated`;路径由 Host 代码解析。Markdown 和代码在追加期间保留同一个增量渲染器,到 EOF 完成最终解析;HTML 和 PDF 接收完整字节。
117
+
118
+ Preview 记录已载入版本和读取开始时的观察版本。刷新只重读当前 tab,不改变共享元数据或其他 tab 的内容。读取不具备事务性;版本是不透明的相等性令牌,不是可排序的时间戳([资源观察与 Preview RPC](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md))。
119
+
120
+ ## 资源模型
121
+
122
+ 模型本身见[客户端资源](./client-resources.md);本节只写 Sidebar 依赖的部分。一份资源是一个地址,资源地址是 `dsh-resource://<type>/…` 形式的 URL,小写 host 即协议键。协议所属的客户端包用 `ctx.resources.register(provider)` 在自身生命周期内注册唯一的提供方;同一协议的第二个提供方抛错([提供协议](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#provide-a-protocol))。提供方是 `{ protocol, open(address, { signal }) }`:`open` 产出 `RemoteResult` 帧——首帧是当前状态,之后每次变化一帧——并在 `signal` 中止时停下;失败是 `{ ok: false, error }` 帧而不是抛错,流里抛出的东西是编程错误,模型不捕获。
123
+
124
+ `useResource<P>(address)` 是每个 slot 组件都有的全局标准 prop,不论作用域。它返回 `{ status, value, failure }`:地址协议没有提供方或地址不是资源地址(`sidebar://guide` 不指向资源)时为 `none`,首帧之前为 `loading`,`live` 携带最新 `ok` 值,`failed` 在最后一个值旁携带最新帧的失败。([读取资源](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#read-a-resource))。
125
+
126
+ 资源有持有者就保持打开——订阅中的 `useResource` 或一次 `ctx.resources.pin(address, signal)`;第一个持有者打开提供方的流,之后的持有者共享它并立刻读到最新值,最后一个释放时中止流并丢弃值。流只推元数据不推内容:`file` 的值是 `{ absolutePath, version, bytes? }`,消费方自己经 Workspace Files 服务按页读文件文本([生命周期](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#lifecycle))。
127
+
128
+ ## Workspace Files
129
+
130
+ Host 的 `ctx.workspaceFiles` 服务与生成的 `workspaceFiles` Remote 命名空间读取 Session 文件系统后端允许的文件:`stat(path)` 返回 `{ absolutePath, version, bytes? }`;`read(path, { offset?, limit? })` 返回一页行(`offset` 1 起,`limit` 受配置页长限制),形如 `{ …stat, offset, text, eof }`;`readBytes(path, { offset?, length? })` 返回一个原始字节窗口(`offset` 0 起,`length` 受配置字节上限限制),形如 base64 的 `{ …stat, offset, data, eof }`、不做文本解码。`list(path)` 仍限定在工作区根内,返回目录的直接子项(`name`、`type: 'file' | 'directory' | 'other'`、`size?`),按配置上限截断并置 `truncated`。`changes()` 同样限定于工作区,订阅就绪后产出 `{ kind: 'ready' }`,随后产出 `{ kind: 'change', change }` 帧,其载荷为 `{ absolutePath, version }` 或 `{ absolutePath, absent: true }`([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md#use-this-package))。文件操作拒绝末端符号链接并执行传输上限;`read` 还要求 UTF-8 文本。失败使用 `workspace-file/*` 错误码([失败](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md))。
131
+
132
+ [`dsh-api-workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) 注册 `file` 提供方,`ResourceProtocolMap.file` 直接是 `WorkspaceFileStat`。Session 地址携带授权 Session 与相对或绝对路径,Host 原样接收并解析。提供方在 stat 前等待 Host 的 `ready` 帧,并按 `stat.absolutePath` 过滤变更。裸 `absolute` 地址没有授权 Session,以 `workspace-file/unknown-workspace` 失败,不借用当前或 Tab Session。任何 UI(包括 Global)访问同一完整地址都共享观察。Preview 的普通 Remote 回调使用地址中的 Session;Host `readAll` 和 `readRelated` 保留,字节结果由 Preview 的 `rpc.ts` 解码。
133
+
134
+ ## 内置类型
135
+
136
+ - **`guide`**——`builtin`,以 `openTab('guide')` 打开。一枚弱化的罗盘位于各类型按 `order` 贡献的入口胶囊上方;入口较少时显示已注册的描述,未提供图标的入口统一使用内置占位符。点选胶囊即在引导 tab 的位置把贡献它的类型作为页面打开。每个 pane 最多一个引导 tab,tab 条的新增控件只在本 pane 没有引导时出现。新 pane 使用已注册的默认页:只有一个引导入口时直接使用该入口,否则使用引导页([引导](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#the-guide))。
137
+ - **`text`**——`fallback`,`dsh-resource://file/**`,只认领 Session 地址。Document Preview 通过 `useResource<'file'>` 观察元数据,经 Remote 回调加载内容,并拥有渲染器选择、工具栏、逐 tab 刷新、滚动与源码定位;未知扩展名按纯文本渲染([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/README.zh.md))。
138
+ - **`files`**——`builtin`,以 `openTab('files')` 打开。工作区目录树,经 `list` 懒加载,用 `tab.actions.openResource(fileAddressFor(sessionId, root, path))` 在自己所在 pane 打开文件([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-files/README.zh.md))。
139
+
140
+ <a id="not-built"></a>
141
+ ## 不做
142
+
143
+ - 持久化:布局状态只在内存里;刷新后每个会话从折叠开始,任何会话的 tab 都不会出现在另一个会话里。
144
+ - `ctx.sidebarRight` 上的只读布局快照或订阅:服务只暴露操作,dockkit 的 `LayoutState`/`LayoutOp` 是内部的。
145
+ - 服务上的能力探测数组(`features`)。
146
+ - tab 类型的 `option` 优先级档:没有「只列出、不许认领」的 tab 类型。
147
+ - 改写记录的标题:`title(address)` 只捕获一次;活的 chip 来自标题 slot,而不是记录。
148
+ - 打开时点名某个 tab 实现:`openResource` 最多点名一个 kind;文档渲染器由文件 tab 的工具栏选择。
149
+ - 服务上的地址查找(`find`):调用方用 `revealIfOpened` 打开,由停靠面去重。
150
+ - Sidebar 自身 `sidebar://<kind>` 记账之外的导航地址;其语法等导航控制器整体做时再定。
151
+ - 面向用户的撤销、内容导航栈与 tab 图标([暂缓](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md#deferred))。
@@ -85,6 +85,7 @@ Slot 声明固定两个相互独立的维度。
85
85
  |---|---|---|
86
86
  | 所有 scope | `useSessions`、`useSessionPendingInteraction` | `ui-session` |
87
87
  | 所有 scope | `useWorkspaces` | `ui-workspace` |
88
+ | 所有作用域 | `usePanelInfo` | `ui-layout` |
88
89
  | `session` | `sessionId`、`useSession`、`useProjection` | `ui-session` |
89
90
  | `session-maybe` | 结果可选的 `sessionId`、`useSession`、`useProjection` | `ui-session` |
90
91
  | `session` | `useConversation`、`useInput`、`inputActions` | `ui-conversation` |
@@ -115,6 +116,7 @@ root
115
116
  ├─ sidebar
116
117
  │ ├─ sidebar.brand.mark
117
118
  │ ├─ sidebar.brand.name
119
+ │ ├─ sidebar.panellist
118
120
  │ ├─ sidebar.footer.action
119
121
  │ ├─ sidebar.workspaces
120
122
  │ │ └─ sidebar.workspaces.directoryFlow
@@ -130,39 +132,45 @@ root
130
132
  │ ├─ settings.models.footer
131
133
  │ └─ settings.plugins.tab
132
134
  │ └─ settings.plugin.item
133
- ├─ conversation
134
- ├─ conversation.session
135
- │ └─ conversation.view
136
- │ ├─ conversation.chat.node
137
- ├─ conversation.chat.assistant-actions
138
- │ │ ├─ conversation.chat.commandview
139
- │ │ ├─ conversation.chat.turnTail
140
- │ │ └─ tool.call.toolview
141
- │ │ ├─ tool.call.images
142
- │ │ └─ tool.view.cordis
143
- ├─ conversation.message.images
144
- └─ conversation.trajectory.images
145
- ├─ conversation.session.header
146
- ├─ conversation.session.header.lineage
147
- │ ├─ conversation.session.header.actions
148
- └─ conversation.session.header.utilities
149
- │ ├─ conversation.composer
150
- │ └─ conversation.approval.detail
151
- ├─ conversation.composer.bar
152
- ├─ conversation.input.attachments
153
- ├─ conversation.input.plan
154
- └─ conversation.input.model
155
- │ ├─ conversation.input.overlay
156
- ├─ conversation.input.dock
157
- ├─ conversation.composer.dock
158
- ├─ conversation.input.left
159
- ├─ conversation.input.right
160
- ├─ conversation.hero.brand.mark
161
- ├─ conversation.hero.workspace
162
- │ └─ conversation.hero.workspace.directoryFlow
163
- └─ conversation.hero.agentPreset
164
- ├─ details
165
- └─ conversation.details.tool
135
+ ├─ main
136
+ └─ main.conversation
137
+ ├─ conversation.session
138
+ └─ conversation.view
139
+ │ ├─ conversation.chat.node
140
+ │ │ ├─ conversation.chat.assistant-actions
141
+ │ │ ├─ conversation.chat.commandview
142
+ │ │ ├─ conversation.chat.turnTail
143
+ │ │ └─ tool.call.toolview
144
+ │ │ ├─ tool.call.images
145
+ │ └─ tool.view.cordis
146
+ ├─ conversation.message.images
147
+ │ └─ conversation.trajectory.images
148
+ ├─ conversation.session.header
149
+ │ ├─ conversation.session.header.lineage
150
+ ├─ conversation.session.header.actions
151
+ ├─ conversation.session.header.utilities
152
+ │ └─ conversation.session.header.corner
153
+ ├─ conversation.composer
154
+ └─ conversation.approval.detail
155
+ ├─ conversation.composer.bar
156
+ ├─ conversation.input.attachments
157
+ ├─ conversation.input.plan
158
+ └─ conversation.input.model
159
+ ├─ conversation.input.overlay
160
+ ├─ conversation.input.dock
161
+ ├─ conversation.composer.dock
162
+ ├─ conversation.input.left
163
+ ├─ conversation.input.right
164
+ ├─ conversation.hero.brand.mark
165
+ ├─ conversation.hero.workspace
166
+ │ │ └─ conversation.hero.workspace.directoryFlow
167
+ └─ conversation.hero.agentPreset
168
+ ├─ rightbar
169
+ │ └─ rightbar.session
170
+ │ ├─ sidebar.right.pane.tab
171
+ │ │ └─ sidebar.right.tab.guide
172
+ │ ├─ sidebar.right.pane.tab.title
173
+ │ └─ sidebar.right.tab.menu.item
166
174
  └─ shell.overlay
167
175
  ```
168
176
 
@@ -5,13 +5,13 @@ outline: [2,3]
5
5
 
6
6
  # spill 存储
7
7
 
8
- spill 存储 seam 是一项[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md),它持久保存工具的超大文本,并返回面向模型的定位符与检索指引;该能力拆分到三个包:Service Definition[dsh-spill](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill),`ctx.spillStore`)、Service Provider[dsh-spill-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-local),宿主文件系统中会话作用域的私有文件)和 Consumer([dsh-spill-policy](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-policy),`tools/post-execute` 策略)。spill 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇记录在此处,而不在 [core.md](./core.md) 中。预览机制仍归 [dsh-output-retention](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/util/output-retention) 所有;该 seam 只保存策略交给它的最终文本。
8
+ spill 存储[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md)持久保存调用方提供的文本,并返回面向模型的定位符与检索指引。其 Service Definition[dsh-spill](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill)(`ctx.spillStore`),本地 Service Provider[dsh-spill-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-local)。消费方包括[工具结果策略](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-policy)[会话引用](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/context/session-reference/README.zh.md)。spill 是可选能力,不属于[智能体循环主干](./core.md);预览与 spill 决策由消费方负责,存储则原样保存所提供的文本。
9
9
 
10
10
  源码:[`packages/spill/spill/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/spill/spill/src/types.ts)
11
11
 
12
12
  ## 保存请求
13
13
 
14
- `saveText` 是唯一的服务操作:原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和准确字节数。请求携带保存时的存储命名空间(`owner`)、生成内容的工具和调用(`source`,用于命名和检查,而非访问控制)以及后端可用作命名提示的 `suggestedName`(它不是路径)。
14
+ `saveText` 是唯一的服务操作:原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和精确字节数。请求携带保存时的存储命名空间(`owner`)、描述性的生产者来源信息(`source`,绝非访问控制)以及后端可用作命名提示而非路径的 `suggestedName`。工具来源标识实际工具调用;会话引用来源标识被捕获的源会话,而其归属是接收上下文的目标会话。
15
15
 
16
16
  ```ts type-equiv
17
17
  /** One request to persist text to a spill artifact. */
@@ -45,17 +45,24 @@ interface SpillOwner {
45
45
 
46
46
  ```ts type-equiv
47
47
  /**
48
- * Tool and call that produced one spilled artifact recorded by the backend for a readable
49
- * filename and inspection. Not interpreted for access control; purely
50
- * descriptive.
48
+ * Producer of a spilled artifact. Tool results carry their model-issued call id;
49
+ * session references identify the captured source session instead. Descriptive
50
+ * provenance only, never access control.
51
51
  */
52
- interface SpillSource {
52
+ type SpillSource = {
53
+ kind: 'tool'
53
54
  /** The tool whose result was spilled (e.g. `web_fetch`). */
54
55
  toolName: string
55
56
  /** The model-issued call id the result belongs to. */
56
57
  callId: ToolCallId
57
58
  /** A short human label for the artifact (e.g. `result`). */
58
59
  label: string
60
+ } | {
61
+ kind: 'session-reference'
62
+ /** Session whose projected conversation was captured. */
63
+ sessionId: SessionId
64
+ /** Host-provided label for the referenced session. */
65
+ label: string
59
66
  }
60
67
  ```
61
68
 
@@ -7,10 +7,12 @@ outline: [2,3]
7
7
 
8
8
  subagent seam 让一个 agent(智能体)将工作委派给子 agent。与 [bash](./shell.md) 一样,它是**一项可选能力**,不属于 agent loop(智能体循环),因此其类型定义在此而非 [core.md](./core.md) 中。它不同于其他能力 seam,因为**同一上下文中可共存多个提供方实现**,并按名称注册(`ctx.subagents`),而 bash 只允许一个执行器。该注册表遵循 [LLM(大语言模型)适配器注册表](./llm-streaming.md),而非单服务的 bash 执行器。
9
9
 
10
- Service Definition:[dsh-subagent](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/subagent)(`ctx.subagents` + 下文词汇)。Service Provider 是六个兄弟包:`dsh-subagent-spawn-in-process`、`dsh-subagent-fork-in-process`、`dsh-subagent-acp`、`dsh-subagent-codex`、`dsh-subagent-claude-code`、`dsh-subagent-dsh-sdk`;面向模型的 Consumer 包括 [dsh-tool-subagent](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent)(按提供方委派)和 [dsh-tool-subagent-control](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent-control)(可选的全局 `send_message`、`interrupt_agent` 与 `list_agents` 控制工具)。同一个 `ctx.subagents` 服务通过内部激活管理器负责可继续子 agent 编排,并直接基于会话存储和可选的会话持久化提供只读的 child 与后代发现。产品提供方设计理由见 [Codex 与 Claude Code Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md);通用 seam 的设计理由见 [subagent Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md)、[可继续 subagent Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md)[相邻 Agent 消息 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md)[持久化目录 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.zh.md)、[列表身份投影 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.zh.md)和[服务合并 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.zh.md)。
10
+ Service Definition:[dsh-subagent](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/subagent)(`ctx.subagents` + 下文词汇)。Service Provider 是六个兄弟包:`dsh-subagent-spawn-in-process`、`dsh-subagent-fork-in-process`、`dsh-subagent-acp`、`dsh-subagent-codex`、`dsh-subagent-claude-code`、`dsh-subagent-dsh-sdk`;面向模型的 Consumer 包括 [dsh-tool-subagent](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent)(按提供方委派)和 [dsh-tool-subagent-control](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent-control)(可选的全局 `send_message`、`interrupt_agent` 与 `list_agents` 控制工具)。同一个 `ctx.subagents` 服务通过内部激活管理器负责可继续子 agent 编排,并直接基于会话存储和可选的会话持久化提供只读的 child 与后代发现。产品提供方设计理由见 [Codex 与 Claude Code Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.zh.md);通用 seam 的设计理由见 [subagent Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.zh.md)、[可继续 subagent Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-28-continuable-subagent-conversations.zh.md)[相邻 Agent 消息 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md)[已归档的列表身份投影记录](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-06-subagent-list-identity-projection.md)记录了最初的列表身份决策。
11
11
 
12
12
  源码:[`packages/subagent/subagent/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/subagent/src/types.ts)、[`packages/subagent/subagent/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/subagent/src/index.ts)和 [`packages/subagent/subagent/src/continuation.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/subagent/src/continuation.ts)
13
13
 
14
+ `subagentCatalog` projection 通过 Session 观察和客户端快照暴露按父会话事件排序的 `SubagentCatalogEntry[]`。每个条目包含子级 id、创建时间、模式和依模式确定的标签;fork 继承的目录事实不在其中。[subagent 包](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subagent/subagent/README.zh.md) 定义目录创建和持久化语义。
15
+
14
16
  ## 两类能力,两种发现方式
15
17
 
16
18
  提供方通过一个静态描述符公布其**启动时**功能,服务会在单次 run 存在之前即行检查;如果请求依赖提供方不具备的功能,会被明确拒绝(`SubagentError('UNSUPPORTED_CAPABILITY')`),绝不会被接受后静默忽略。这些 flag 仅描述单次 [`start()`](#the-provider-contract-subagentprovider) 路径,即由提供方组合子 agent 的路径。**可继续**子 agent 由继续执行管理器自行组合,因此它们由唯一一个可选方法把关,方法存在即为能力,并以 TypeScript 的类型收窄作为发现机制:[`SubagentProvider.prepareContinuable`](#the-provider-contract-subagentprovider)。
@@ -99,7 +101,7 @@ interface SubagentStartRequest {
99
101
  /**
100
102
  * Optional per-child persona. Requires {@link SubagentCapabilities.persona};
101
103
  * rejected at start otherwise. In-process backends register it as a scoped
102
- * `deployment:persona` section on the child, SHADOWING the deployment's
104
+ * `deployment:persona-prefix` section on the child, SHADOWING the deployment's
103
105
  * persona for this child alone — same template semantics as the deployment
104
106
  * persona (strict `{{…}}` interpolation against the registered variables).
105
107
  */
@@ -144,13 +146,15 @@ persisted Session
144
146
  | `waiting` | 唤醒并 steer 同一 Activation |
145
147
  | 无 Activation | 冷恢复新的 Activation,然后 steer |
146
148
 
147
- `running` 表示 Agent 拥有活跃的准入或轮次,或正在唤醒收件箱工作;`waiting` 表示它已完全停稳,但仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示已完全停稳且其拥有的每个子级都已 dispose,此时管理器会 dispose [`AgentHandle`](./core.md#creation-and-ownership) 并移除该 Activation。管理器根据 Agent 的完全停稳状态与其拥有的子级集合推导这些内部条件,而非维护第二套执行状态机。
149
+ `running` 表示 Agent 拥有活跃的 driver 或 maintenance 任务;`waiting` 表示没有活跃的 Agent 工作,但其 Inbox 非空或仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示没有活跃的 Agent 工作、Inbox 为空且其拥有的每个子级都已 dispose,此时管理器会 dispose [`AgentHandle`](./core.md#creation-and-ownership) 并移除该 Activation。管理器根据 `Agent.whenIdle()`、`Agent.inbox.hasPending`、其拥有的子级集合,以及让过期观察失效的 Activation generation 推导这些内部条件,而非维护第二套执行状态机。最终 Session flush 之后,child-lock 决策会通过 `Agent.runMaintenance()` 的同步 task 入口占用 idle 阶段,并在同一个 JavaScript turn 内关闭准入。这条保守规则不区分投递模式:`Agent.inject()` 停放的 context 可以让空闲 Activation 及其在线祖先继续驻留,直到唤醒投递将其 claim、queue 变更将其移除,或 manager teardown 将其丢弃。
148
150
 
149
- Agent 收件箱是唯一队列。每条 Agent 消息都使用 `Agent.steer()`:空闲目标会启动一个轮次,运行中目标则在最近的 step 边界领取消息。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/inserted`、`agent/inbox/claimed` 与 `agent/inbox/discarded` 事件仍是消息生命周期的观测点,继续执行层不定义任何 subagent 专属的投递路由。
151
+ Agent 收件箱是唯一队列。每条 Agent 消息都使用 `Agent.steer()`:空闲目标会启动一个轮次,运行中目标则在最近的 step 边界领取消息。浏览器 `subagent.prompt` Remote 会另行通过同一条内部准入路径携带 `delivery: 'queue' | 'steer'`;Queue 开启后续 FIFO 轮次,Steer 保留 Agent loop 的 best-effort 最近 step 行为以及消息的人类来源。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/inserted`、`agent/inbox/claimed` 与 `agent/inbox/discarded` 事件仍是消息生命周期的观测点,继续执行层不定义第二条队列。
150
152
 
151
153
  权限来自确切在线 sender。parent 到 child 的投递要求目标的 `SessionHeader.parentSession` 指向 sender;child 到 parent 的投递要求 sender 的驻留 Activation 指向目标。sibling、相隔多于一条边的 ancestor、self-target、陈旧 Agent 对象与一次性 child 都会被拒绝。每条已接受消息都以 `Agent <sender-id> sent a message:` 作为前缀,并记录 `AgentMessageSource`;来源信息记录 sender,但不授予权限。
152
154
 
153
- 对于 `startContinuable()` 与 `sendMessage()`,调用方 signal 仅在收件箱接受之前掌管查找、物化与准入。此后管理器独立掌管该 Activation:之后的调用方取消既不会取消已接受的轮次,也不会 dispose 子 agent。浏览器中的人类提示仍由私有 Queue 适配器处理,因此继续产生独立 FIFO 轮次。
155
+ 对于 `startContinuable()`、`sendMessage()` 与浏览器 prompt 投递,调用方 signal 仅在收件箱接受之前掌管查找、物化与准入。此后管理器独立掌管该 Activation:之后的调用方取消既不会取消已接受的轮次,也不会 dispose 子 agent。公开 subagent 服务不暴露由调用方选择的 Agent 消息调度;浏览器人类 Queue Steer 仍是内部适配器选择。
156
+
157
+ 在线 queue occurrence 变更属于 Session 域。只有在线 subagent-owned Agent 的当前 projection identity 为 continuable,且其 descriptor 序号位于该 child 自身的非 seed suffix 时,`session.updateQueue` 才会接纳普通 Edit、Remove 与 QueueDock Steer。Identity projection 以 last-wins 方式折叠 descriptor,因此 child descriptor 会覆盖 fork lineage 保留的 descriptor;own-suffix 序号检查会阻止仅来自 seed 的祖先 identity 授权变更。One-shot、缺失、未知、损坏或冷 child 会被拒绝,queue 变更绝不会冷恢复 child。这些变更以目标 Session id 作为人类权限,包括待处理 `nextStep` steering 或注入 context。Steer 要求 queued `MessageId`,且 command 开始时 Agent 必须报告 running;准入后发生取消时,会使用 Agent 已接受的唤醒 `nextTurn` fallback。Edit 会在同一个 `MessageId` 下改写内容,且 Edit 与 Steer 都会同步完成 Inbox 变更,因此 settlement 只会观察最终状态。`agent/inbox/claimed` 与 `agent/inbox/discarded` 都会唤醒 watcher 重新读取是否仍有待处理 occurrence;这样,直接 Agent 投递可以恢复停放工作,而移除最后一个停放 occurrence 可使 idle child 结算。[人类 inbox 控制 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md)拥有这些语义。
154
158
 
155
159
  `SubagentRuntime.interrupt(targetSessionId, authority)` 是唯一的公开停止操作:它同步完成鉴权,对在线目标发出 `Agent.cancel(cause, { keepInbox: true })`,然后不等待完全停稳即返回。Activation、其尚未领取的待处理 inbox 工作与已发布的后代均不受影响;已被领取进入中断轮次的工作不会重新入队。被中断的 driver 进入 idle 后,一次唤醒发送会恢复被暂停的 FIFO 队列。不存在的目标——未知、一次性或已结算——以及未绑定管理器的组合是被接受的 no-op。对在线目标,错误的 parent 地址或不在其在线祖先链中的调用方会以 `UNAUTHORIZED` 拒绝;陈旧的 ancestor 对象和指向自身的 ancestor 请求会在查找目标前拒绝。
156
160
 
@@ -165,7 +169,7 @@ type SubagentInterruptAuthority =
165
169
  | { readonly kind: 'ancestor'; readonly agent: Agent }
166
170
  ```
167
171
 
168
- 每个 Activation 都拥有自己的 `AgentHandle` 和一个 `ownedChildren: Set<SessionId>`;由于一份会话至多有一个存活 Activation,子会话 id 无需另一个运行时化身引用即可标识存活的子 agent。启动子 agent 或提交源自 parent 的工作,会在子 agent 能够运行之前将其注册到受继续执行管理的父级集合中;只要该集合非空,该父级就无法 settle。顶层或其他非继续执行的 Agent 没有 Activation,处于 waiting 图之外。只有当子 Agent 已完全停稳、该子 agent 的每个子级都已 dispose、best-effort 的最终会话 flush 结算完毕,且子 agent 的 `AgentHandle` 完成 dispose 之后,才会释放子 agent。
172
+ 每个 Activation 都拥有自己的 `AgentHandle` 和一个 `ownedChildren: Set<SessionId>`;由于一份会话至多有一个存活 Activation,子会话 id 无需另一个运行时化身引用即可标识存活的子 agent。启动子 agent 或提交源自 parent 的工作,会在子 agent 能够运行之前将其注册到受继续执行管理的父级集合中;只要该集合非空,该父级就无法 settle。顶层或其他非继续执行的 Agent 没有 Activation,处于 waiting 图之外。只有当子 Agent 没有活跃工作、其 Inbox 为空、该子 agent 的每个子级都已 dispose、best-effort 的最终会话 flush 结算完毕,且子 agent 的 `AgentHandle` 完成 dispose 之后,才会释放子 agent。
169
173
 
170
174
  最终结算会等待 `ctx.sessions.flush(session)`,但会忽略其参与布尔值,因为任意 listener 都无法证明某个持久化后端已存储该状态。rejection 会被记录,但不会使 Activation 失败;管理器仍会 dispose 该 handle 并释放所有权,此后持久化的子 agent 状态在后续恢复时可能缺失或陈旧。管理器卸载会调用内部的管理器全局 drain,关闭准入并 dispose 每片在线森林;`drainContinuableDescendants(parents)` 只关闭由 host 确切拥有的在线 Agent 之下的准入,并 dispose 其可继续后代,而无关森林保持在线。两者都会等待各自作用域内已获准的物化过程,自顶向下传播取消,按 child-first 顺序释放 handle,并且即使个别分支失败也会等待所有选中分支。持久化子会话不受该进程内拆卸的影响。
171
175
 
@@ -198,7 +202,7 @@ interface ContinuableStart {
198
202
  }
199
203
  ```
200
204
 
201
- 当驻留 Activation 结算时,管理器会向该 child 持久化的直接 parent 投递一条通知,说明该 epoch 如何结束,并携带其最终 assistant 内容。对每个调用方拿到过 id 的 child,这条投递都是无条件的;它发生在会让 parent 被判定为已结算的所有权释放之前,并通过与 Agent 消息相同的唤醒准入记账到达驻留 parent。若 parent 自身所在的谱系已在拆卸中,这条通知会以不唤醒的方式送达,因为唤醒一个静息 Agent 是开启一个轮次,而不是排队等待工作。其来源信息使用一个独立的 kind,因此 transcript(文本记录)绝不会把运行时的记账呈现为 child 自己写下的内容。
205
+ 当驻留 Activation 结算时,管理器会向该 child 持久化的直接 parent 投递一条通知,说明该 epoch 如何结束,并携带其最终 assistant 内容。对每个调用方拿到过 id 的 child,这条投递都是无条件的;它发生在会让 parent 被判定为已结算的所有权释放之前,并通过与 Agent 消息相同的唤醒 Agent 投递到达驻留 parent。若 parent 自身所在的谱系已在拆卸中,这条通知会以不唤醒的方式送达,因为唤醒一个 idle Agent 是开启一个轮次,而不是排队等待工作。其来源信息使用一个独立的 kind,因此 transcript(文本记录)绝不会把运行时的记账呈现为 child 自己写下的内容。
202
206
 
203
207
  ```ts type-equiv
204
208
  /**
@@ -265,7 +269,7 @@ interface ContinuableCreateSpec {
265
269
 
266
270
  ## 持久化枚举:`listChildren()`、`listDescendants()` 与其条目
267
271
 
268
- `SubagentRuntime.listChildren(parentSessionId)` 从 `ctx.sessions` 与会话查询引擎 `listSessions()` 的实时优先合并中枚举 parent 直接且由会话支撑的 subagent——不会加载或恢复任何 Agent。候选是持久 header 携带 `origin: 'subagent'` 的直接 child;该标记只负责枚举分类与粗粒度的通用路由拒绝,不能证明描述符有效、child 可恢复或操作已获授权——身份由投影折叠负责,恢复由 Activation 约定负责。每行的 `mode`/`label` 是已注册 `subagent` projection unit 的值,经三级阶梯供值:存活 child 由注册表水位缓存供值(零日志读取);冷 child 先读可选的投影 checkpoint 缓存(`cachedSnapshot`——过 own-suffix seq 门的身份即定值,own descriptor 一经追加不可变);否则在一次 `query.observeSession()` 冷观察上经注册表折叠(有界并发,每次列表重新计算)。该缓存是纯可选加速层:服务缺席、行里是 `null` 哨兵或 key 缺席、seq 门不过、读取出错,都静默落到权威重折。折叠规则是 `subagent/descriptor` last-wins 且没有失败通道:子 agent 自己的描述符覆盖 fork seed 中祖先的描述符,格式错误或版本不认识的载荷折叠为可序列化的 `null` 哨兵,视同无值。结果是按 `createdAt`、再按 id 排序的 `SubagentListEntry[]`:取到身份即生成带有 `mode: 'one-shot' | 'continuable'` 和 `activity: 'running' | 'inactive'` 的 `child` 条目;可继续条目始终携带 `label`,一次性条目则只在启动调用方提供展示元数据时携带该字段。已定局而折叠无身份的候选生成 `corrupt` diagnostic——缺失、格式错误与版本不认识的描述符有意不再细分(`unsupported` 仍保留在类型中但从不产出);运行中而无身份的候选被省略(描述符落盘前的创建窗口);冷检查失败生成一条 `unavailable` diagnostic 并在下次列表自然重试,因此一个损坏的 sibling 不会隐藏健康 child。`hasChildren` 标记存在持久 subagent origin 的直接后代,读取自同一份合并材料。活动状态只表示逻辑记录是否在 `ctx.sessions` 中存活,而不表示结果或可恢复性。缺少持久化时,枚举退化为仅存活枚举而不是报错——此时冷 child 本就无法恢复。缺少 `ctx.sessionProjections` 注册表时,`listChildren()` 抛出携带错误码 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` 的 `SubagentError`,缺少会话存储时则抛出 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`,两者都在任何读取之前检查,因此零 child 的部署同样确定失败;列表工具在插件加载时要求 `ctx.subagents` 与 `ctx.agents`。UI 等服务消费方可以展示两种模式,并为无标签的一次性 child 选择回退展示;面向模型的 `list_agents` 适配器([dsh-tool-subagent-control](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent-control) 中可单独加载的 `/list-agents` 插件)则只保留可继续条目,并通过在线 Agent 注册表将状态细化为自己的 `running`/`idle`/`ready` 词汇,其中 `ready` 把仅存于存储的 child 命名为可恢复而非终态。枚举不会查询继续执行管理器的 Activation map、Agent 注册表或提供方可用性;`send_message` 仍是消息送达时的权威操作,列表中的运行中可继续 child 仍可能因所有权冲突而拒绝投递。读路径的设计理由见[列表身份投影 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.zh.md)
272
+ `SubagentRuntime.listChildren(parentSessionId)` 从 `ctx.sessions` 与会话查询引擎 `listSessions()` 的实时优先合并中枚举 parent 直接且由会话支撑的 subagent——不会加载或恢复任何 Agent。候选是持久 header 携带 `origin: 'subagent'` 的直接 child;该标记只负责枚举分类与粗粒度的通用路由拒绝,不能证明描述符有效、child 可恢复或操作已获授权——身份由投影折叠负责,恢复由 Activation 约定负责。每行的 `mode`/`label` 是已注册 `subagent` projection unit 的值,经三级阶梯供值:存活 child 由注册表水位缓存供值(零日志读取);冷 child 先读可选的投影 checkpoint 缓存(`cachedSnapshot`——过 own-suffix seq 门的身份即定值,own descriptor 一经追加不可变);否则在一次 `query.observeSession()` 冷观察上经注册表折叠(有界并发,每次列表重新计算)。该缓存是纯可选加速层:服务缺席、行里是 `null` 哨兵或 key 缺席、seq 门不过、读取出错,都静默落到权威重折。折叠规则是 `subagent/descriptor` last-wins 且没有失败通道:子 agent 自己的描述符覆盖 fork seed 中祖先的描述符,格式错误或版本不认识的载荷折叠为可序列化的 `null` 哨兵,视同无值。结果是按 `createdAt`、再按 id 排序的 `SubagentListEntry[]`:取到身份即生成带有 `mode: 'one-shot' | 'continuable'` 和 `activity: 'running' | 'inactive'` 的 `child` 条目;可继续条目始终携带 `label`,一次性条目则只在启动调用方提供展示元数据时携带该字段。已定局而折叠无身份的候选生成 `corrupt` diagnostic——缺失、格式错误与版本不认识的描述符有意不再细分(`unsupported` 仍保留在类型中但从不产出);运行中而无身份的候选被省略(描述符落盘前的创建窗口);冷检查失败生成一条 `unavailable` diagnostic 并在下次列表自然重试,因此一个损坏的 sibling 不会隐藏健康 child。`hasChildren` 标记存在持久 subagent origin 的直接后代,读取自同一份合并材料。活动状态只表示逻辑记录是否在 `ctx.sessions` 中存活,而不表示结果或可恢复性。缺少持久化时,枚举退化为仅存活枚举而不是报错——此时冷 child 本就无法恢复。缺少 `ctx.sessionProjections` 注册表时,`listChildren()` 抛出携带错误码 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` 的 `SubagentError`,缺少会话存储时则抛出 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`,两者都在任何读取之前检查,因此零 child 的部署同样确定失败;列表工具在插件加载时要求 `ctx.subagents` 与 `ctx.agents`。UI 等服务消费方可以展示两种模式,并为无标签的一次性 child 选择回退展示;面向模型的 `list_agents` 适配器([dsh-tool-subagent-control](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/subagent/tool-subagent-control) 中可单独加载的 `/list-agents` 插件)则只保留可继续条目,并通过在线 Agent 注册表将状态细化为自己的 `running`/`idle`/`ready` 词汇,其中 `ready` 把仅存于存储的 child 命名为可恢复而非终态。枚举不会查询继续执行管理器的 Activation map、Agent 注册表或提供方可用性;`send_message` 仍是消息送达时的权威操作,列表中的运行中可继续 child 仍可能因所有权冲突而拒绝投递。[已归档的列表身份投影记录](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/archived/architecture/2026-08-06-subagent-list-identity-projection.md)记录了最初的读路径决策。
269
273
 
270
274
  `SubagentRuntime.listDescendants(rootSessionId)` 将同一份实时优先语料与基于投影的解释应用到根的完整后代树,并按稳定 pre-order 输出。普通会话和一次性 child 仍作为遍历节点,因此其下的可继续后代仍可发现;只有 `origin: 'subagent'` 的候选会生成条目。每个返回的 child 或 diagnostic 都从枚举所得的持久 header 附加树位置;冷检查在提供身份前还会重新校验完整生命周期:
271
275
 
@@ -476,11 +480,11 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
476
480
 
477
481
  ### `ctx.subagentModelSelection` — `SubagentModelSelectionConfig`
478
482
 
479
- Singleton settings owner read by delegation tools when an Agent is published.
483
+ Singleton settings owner read when delegation tools are composed for a Session.
480
484
 
481
485
  ```ts cordis-catalog
482
486
  /**
483
- * Read a detached selection preference for the next eligible Agent publication.
487
+ * Read a detached selection preference for the next eligible Session composition.
484
488
  * @returns the enabled state and exact allowed routes.
485
489
  */
486
490
  current(): SubagentModelSelectionSettings
@@ -618,11 +622,12 @@ listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise<Subagen
618
622
  * Deliver one browser-authored message to a continuable child through the
619
623
  * exact live direct parent, retaining the caller-minted request identity and
620
624
  * validated browser zone on the accepted message. Success identifies the
621
- * message the child's FIFO inbox accepted; later execution is independent of
622
- * this call.
625
+ * message the child's inbox accepted; later execution is independent of this
626
+ * call. Queue delivery targets a later turn; steer delivery targets the
627
+ * nearest step and retains the Agent loop's best-effort fallback semantics.
623
628
  * Image parts are admitted and persisted through the attachment store
624
629
  * before delivery, and the child's model must accept image input.
625
- * @param request - durable address, minted identity, content, and optional browser zone.
630
+ * @param request - durable address, delivery, minted identity, content, and optional browser zone.
626
631
  * @param signal - carrier cancellation, owning the call until inbox acceptance.
627
632
  * @returns the accepted message's inbox identity.
628
633
  * @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
@@ -676,6 +681,8 @@ list(): string[]
676
681
  * fulfills; a rejection therefore has no run for the caller to dispose and
677
682
  * emits no run lifecycle events. Post-publication turn and infrastructure
678
683
  * failures settle through the returned run.
684
+ * A catalog append failure disposes the run and handles its result rejection;
685
+ * the caller receives the catalog error even if disposal also fails.
679
686
  * @param name - the provider to use.
680
687
  * @param request - child label, prompt, parent, signal, and optional capabilities.
681
688
  * @returns the published holder-owned run.
@@ -109,14 +109,15 @@ interface SubprocessSpawnSpec {
109
109
  stdio: SubprocessStdio
110
110
  /**
111
111
  * Positive finite grace period in milliseconds, no greater than
112
- * `MAX_TIMER_DELAY_MS`, for the {@link SubprocessHandle.terminate} escalation
113
- * and for draining still-open collected pipes after the process exits (an
114
- * inherited descriptor held by a surviving descendant cannot hold the
115
- * outcome open indefinitely).
112
+ * `MAX_TIMER_DELAY_MS`, available to the provider's termination procedure
113
+ * and used for draining still-open collected pipes after the process exits
114
+ * (an inherited descriptor held by a survivor cannot hold the outcome open
115
+ * indefinitely). Providers document whether range termination is staged or
116
+ * immediate.
116
117
  */
117
118
  graceMs: number
118
119
  /**
119
- * Abort signal — starts the terminate escalation on the process tree when
120
+ * Abort signal — starts the terminate escalation on the managed range when
120
121
  * it fires. The caller owns deadlines and cause classification; this seam
121
122
  * only reacts to the abort.
122
123
  */
@@ -132,23 +133,20 @@ interface SubprocessSpawnSpec {
132
133
  }
133
134
  ```
134
135
 
135
- ## 句柄:流、读取器与以进程树为范围的终止
136
+ ## 句柄:流、读取器与 managed-range 终止
136
137
 
137
- spawn 会立即返回一个活动句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`terminate()`(唯一的终止动词)执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树。这足以让消费方构建自己的分级清理流程;ACP 后端的 `disposeAcpChild` 会先关闭 stdin,让子进程收到 EOF,是仓库内的参考实现。
138
+ spawn 会同步返回活动句柄,目标与受管范围标识则保留在 provider 内部。收集模式的读取器接受全流字节偏移量且从不消费,因此独立读取器不会抢走彼此的增量;管道化的流归调用方所有。`terminate()` 启动 provider 记录的终止过程,`waitForExit()` 观察同一个 provider-managed range;分阶段 provider 可以使用 `graceMs`,立即终止的 provider 不会等待。消费方可以在这两项操作上构建自己的分级清理流程;ACP 后端先关闭 stdin 的 `disposeAcpChild` 是参考实现。
138
139
 
139
140
  ```ts type-equiv
140
141
  /**
141
- * A live child process rooted in its own process tree. Collected output
142
+ * A live subprocess and its provider-managed process range. Collected output
142
143
  * remains readable after exit; piped streams belong to the caller.
143
144
  *
144
- * Termination is tree-scoped everywhere: POSIX signals the detached process
145
- * group (falling back to the direct child when the group is gone), Windows
146
- * terminates the tree via `taskkill /T`, so helper processes cannot outlive
147
- * the handle unnoticed.
145
+ * Termination and {@link SubprocessHandle.waitForExit} use the same managed
146
+ * range. Each provider documents the range it can observe and its signalling
147
+ * and observation limits.
148
148
  */
149
149
  interface SubprocessHandle {
150
- /** Process id (tree root); -1 when the spawn itself failed. */
151
- readonly pid: number
152
150
  /** The child's stdin, present iff spawned with `stdin: 'pipe'`. */
153
151
  readonly stdin: Writable | undefined
154
152
  /** The child's raw stdout, present iff spawned with `stdout: 'pipe'`. */
@@ -157,20 +155,20 @@ interface SubprocessHandle {
157
155
  readonly stderr: Readable | undefined
158
156
  /** Offset-based readers for collect-mode streams (also readable after exit). */
159
157
  readonly collected: SubprocessCollectedOutputs
160
- /** Resolves at process close with exit facts; rejects only for spawn-level failures. */
158
+ /** Resolves with spawned-command exit facts; rejects for spawn or provider failures. */
161
159
  readonly done: Promise<SubprocessOutcome>
162
160
  /**
163
- * Begin the SIGTERM `graceMs` SIGKILL escalation on the process tree
164
- * (Windows force-terminates immediately) — the seam's only termination
165
- * verb. Idempotent, a no-op once the tree is gone (the pid may be reused),
166
- * and also triggered by the spec's abort signal.
161
+ * Begin the provider's documented termination procedure on the managed range
162
+ * — the seam's only termination verb. Idempotent, a no-op once that range is
163
+ * gone, and also triggered by the spec's abort signal.
167
164
  */
168
165
  terminate(): void
169
166
  /**
170
- * Wait until the process tree has exitedthe tree, not just the direct
171
- * child, so a still-running helper is observable before teardown returns.
167
+ * Wait until the same managed range is empty — not just until the spawned
168
+ * command reports its outcome, so surviving work remains observable.
172
169
  * @param signal - optional bound for the wait.
173
- * @returns `true` when the tree exited, `false` when the signal aborted first.
170
+ * @returns `true` when the managed range is empty, `false` when the signal aborted first.
171
+ * @throws when the selected provider can no longer observe its managed range.
174
172
  */
175
173
  waitForExit(signal?: AbortSignal): Promise<boolean>
176
174
  }
@@ -249,7 +247,7 @@ interface SubprocessOutcome {
249
247
 
250
248
  ## 服务行为
251
249
 
252
- 抽象的 [`SubprocessRuntime`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess/src/index.ts) Service Definition 规定执行世界坐标、可执行文件查找、普通 `spawn` 与 `spawnTerminal`。[`LocalSubprocessRuntime`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess-local/src/index.ts) detached 进程树、按处置方式接线、凭据清除、`node-pty`、平台进程检查,以及先终止再等待退出的资源释放提供这些能力。Service Definition 约定见 [`dsh-subprocess`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess/README.zh.md),本地机制见 [`dsh-subprocess-local`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess-local/README.zh.md)。
250
+ 抽象的 [`SubprocessRuntime`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess/src/index.ts) Service Definition 规定执行世界坐标、可执行文件查找、普通 `spawn` 与 `spawnTerminal`。[`LocalSubprocessRuntime`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess-local/src/index.ts) 以平台选择的 managed range、按处置方式接线、凭据清除、`node-pty`、平台进程检查,以及先终止再等待退出的资源释放提供这些能力。Service Definition 约定见 [`dsh-subprocess`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess/README.zh.md),本地机制见 [`dsh-subprocess-local`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/subprocess/subprocess-local/README.zh.md)。
253
251
 
254
252
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
255
253
 
@@ -285,9 +283,9 @@ Abstract subprocess service. Subclass, implement spawn, and load the subclass as
285
283
  Implementations must honor these semantics:
286
284
 
287
285
  - Executable paths belong to one execution world shared with the mounted filesystem provider.
288
- - spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures.
286
+ - spawn returns a live handle synchronously. Target identity remains provider-private; `done` resolves with the spawned command's exit facts and may reject for spawn or provider failures.
289
287
  - Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists. Piped streams are handed to the caller raw and never buffered here.
290
- - SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence.
288
+ - SubprocessHandle.terminate (and the spec's abort signal) starts the provider's documented procedure against its managed range. SubprocessHandle.waitForExit observes that same range so a consumer-owned teardown ladder can hold each tier on real quiescence; each provider documents its signalling and observability limits.
291
289
  - Disposal of the service terminates all still-running managed processes and awaits their exit.
292
290
  - spawnTerminal owns terminal allocation, text transport, foreground groups, signalling, and whole-session quiescence behind one awaited termination method; readiness and persistent-shell policy stay in the PTY consumer. Its output stream ends after queued terminal output when the top-level process exits.
293
291
 
@@ -310,13 +308,14 @@ abstract resolveExecutable( command: string, env?: Readonly<Record<string, strin
310
308
  * applies no defaults.
311
309
  * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment.
312
310
  * @returns the live process handle (streams/readers, signalling, outcome promise).
311
+ * @throws synchronously when pre-aborted or when argv, cwd, environment, or grace is invalid before handle creation.
313
312
  */
314
313
  abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle
315
314
 
316
315
  /**
317
316
  * Allocate a real terminal and start one owned process session. This is the
318
317
  * only non-pipe process primitive: implementations own terminal byte I/O,
319
- * foreground groups, signals, and complete session-tree cleanup.
318
+ * foreground groups, signals, and whole-session quiescence.
320
319
  * @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation.
321
320
  * @returns the live terminal handle after allocation succeeds.
322
321
  */