فهرست منبع

Merge remote-tracking branch 'origin/master' into worktree/steer-followup-images

creatixchu 4 هفته پیش
والد
کامیت
3b2e5105e6
100فایلهای تغییر یافته به همراه800 افزوده شده و 585 حذف شده
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml
  2. 1 1
      .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md
  3. 1 1
      .agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md
  4. 2 2
      .agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.i18n.yaml
  5. 23 19
      .agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.md
  6. 29 23
      .agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.zh.md
  7. 6 0
      .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml
  8. 35 0
      .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md
  9. 35 0
      .agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md
  10. 2 2
      .agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.i18n.yaml
  11. 2 2
      .agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.md
  12. 2 2
      .agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.zh.md
  13. 2 2
      .agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml
  14. 7 6
      .agents/notes/implemented/feature/2026-07-06-sandbox.md
  15. 10 9
      .agents/notes/implemented/feature/2026-07-06-sandbox.zh.md
  16. 2 2
      .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml
  17. 4 4
      .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md
  18. 4 4
      .agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md
  19. 2 2
      .agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml
  20. 2 2
      .agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md
  21. 3 3
      .agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.zh.md
  22. 2 2
      .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml
  23. 1 1
      .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md
  24. 1 1
      .agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md
  25. 2 2
      .agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml
  26. 1 1
      .agents/notes/implemented/feature/2026-07-31-web-default-search.md
  27. 1 1
      .agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md
  28. 2 2
      .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml
  29. 1 1
      .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md
  30. 1 1
      .agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md
  31. 2 2
      .agents/notes/implemented/process/2026-06-11-quality-gates.i18n.yaml
  32. 1 1
      .agents/notes/implemented/process/2026-06-11-quality-gates.md
  33. 1 1
      .agents/notes/implemented/process/2026-06-11-quality-gates.zh.md
  34. 2 2
      .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml
  35. 2 2
      .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md
  36. 2 2
      .agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md
  37. 2 2
      .agents/notes/implemented/process/2026-07-13-documentation-site-projection.i18n.yaml
  38. 1 1
      .agents/notes/implemented/process/2026-07-13-documentation-site-projection.md
  39. 1 1
      .agents/notes/implemented/process/2026-07-13-documentation-site-projection.zh.md
  40. 2 2
      .agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml
  41. 2 2
      .agents/notes/implemented/process/2026-07-20-gui-testing-system.md
  42. 2 2
      .agents/notes/implemented/process/2026-07-20-gui-testing-system.zh.md
  43. 2 2
      .agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.i18n.yaml
  44. 1 1
      .agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.md
  45. 1 1
      .agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.zh.md
  46. 2 2
      .agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.i18n.yaml
  47. 1 1
      .agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.md
  48. 1 1
      .agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.zh.md
  49. 2 2
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml
  50. 0 0
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md
  51. 0 0
      .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md
  52. 2 2
      .agents/notes/implemented/process/2026-08-10-vendor-package-rescope.i18n.yaml
  53. 1 1
      .agents/notes/implemented/process/2026-08-10-vendor-package-rescope.md
  54. 1 1
      .agents/notes/implemented/process/2026-08-10-vendor-package-rescope.zh.md
  55. 2 2
      .agents/notes/implemented/process/2026-08-12-face-named-client-test-files.i18n.yaml
  56. 1 1
      .agents/notes/implemented/process/2026-08-12-face-named-client-test-files.md
  57. 1 1
      .agents/notes/implemented/process/2026-08-12-face-named-client-test-files.zh.md
  58. 2 2
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml
  59. 2 2
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md
  60. 2 2
      .agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md
  61. 6 0
      .agents/notes/implemented/process/2026-08-19-remove-knip.i18n.yaml
  62. 27 0
      .agents/notes/implemented/process/2026-08-19-remove-knip.md
  63. 27 0
      .agents/notes/implemented/process/2026-08-19-remove-knip.zh.md
  64. 2 2
      .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml
  65. 1 1
      .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md
  66. 1 1
      .agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md
  67. 2 2
      .agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml
  68. 1 4
      .agents/notes/proposed/process/2026-06-20-discover-package-inventory.md
  69. 1 4
      .agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md
  70. 1 1
      .agents/skills/dsh-find-simplifications/SKILL.md
  71. 22 0
      .github/workflows/ci.yml
  72. 1 0
      .gitignore
  73. 1 1
      AGENTS.md
  74. 0 1
      THIRD_PARTY_NOTICES.md
  75. 1 0
      apps/cli/tests/built-bin.e2e.ts
  76. 1 2
      apps/cli/tests/fixtures/dsh-badge/snapshot.ts
  77. 2 0
      apps/cli/tests/profiles/headless/tests/harness.ts
  78. 3 0
      apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
  79. 2 2
      docs/architecture.i18n.yaml
  80. 2 0
      docs/architecture.md
  81. 2 0
      docs/architecture.zh.md
  82. 2 2
      docs/config-catalog.i18n.yaml
  83. 36 30
      docs/config-catalog.md
  84. 37 31
      docs/config-catalog.zh.md
  85. 2 2
      docs/cookbook/adding-a-package.i18n.yaml
  86. 0 1
      docs/cookbook/adding-a-package.md
  87. 0 1
      docs/cookbook/adding-a-package.zh.md
  88. 2 2
      docs/event-producer-consumer.i18n.yaml
  89. 1 1
      docs/event-producer-consumer.md
  90. 1 1
      docs/event-producer-consumer.zh.md
  91. 2 2
      docs/module-graph.i18n.yaml
  92. 177 161
      docs/module-graph.md
  93. 178 162
      docs/module-graph.zh.md
  94. 2 2
      docs/persistence-catalog.i18n.yaml
  95. 8 8
      docs/persistence-catalog.md
  96. 8 8
      docs/persistence-catalog.zh.md
  97. 2 2
      docs/subsystems/goal.i18n.yaml
  98. 1 1
      docs/subsystems/goal.md
  99. 1 1
      docs/subsystems/goal.zh.md
  100. 2 2
      docs/subsystems/permission-presets.i18n.yaml

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.i18n.yaml

@@ -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/architecture/2026-08-04-draft-provider-endpoint-interrogation.md
-2026-08-04-draft-provider-endpoint-interrogation.md: a93c5f79a0e4f824be2f1e96853204d27cd22845
-2026-08-04-draft-provider-endpoint-interrogation.zh.md: f8e9002b5267a9d13425601eaefff29e45662699
+2026-08-04-draft-provider-endpoint-interrogation.md: 502d9bab15dcb91a59deb26443d869a36b028b48
+2026-08-04-draft-provider-endpoint-interrogation.zh.md: e0605369c7f4707eb682cc1c32d11123140b449a

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.md

@@ -47,4 +47,4 @@ What it costs: the wire gained a third secret-carrying payload, so the configura
 
 ## Testing
 
-`packages/llm/llm/tests/topology.spec.ts` covers the registry: one offer per namespace, disposal with the fiber, normalization that drops duplicate and unusable ids without inventing capacities, and the `NO_DISCOVERY`/`INVALID_DISCOVERY` refusals. `packages/llm/llm-pi-ai/tests/discovery.spec.ts` drives the probe against local HTTP servers — a listing with and without disclosed capacities, a preserved deployment path, an absent credential, a configured route supplying its own where the draft has none and a typed key winning over it, a catalog route answering without resolving one at all, dropped rows, 401/403 versus a server fault, a non-listing and a non-JSON body, an unreachable endpoint, caller cancellation, an unsupported protocol, and the size ceiling in both its declared-length and streamed forms. `packages/api/settings-controller/tests/` covers the RPC over a real proxy: the draft reaching its namespace whole, absent fields staying absent, no namespace or credential being written, and a failure surfacing as `model-discovery-failed` with the credential absent from the serialized error.
+`packages/llm/llm/tests/topology.spec.ts` covers the registry: one offer per namespace, disposal with the fiber, normalization that drops duplicate and unusable ids without inventing capacities, the `NO_DISCOVERY`/`INVALID_DISCOVERY` refusals, and the `model-discovery-failed` Remote mapping. `packages/llm/llm-pi-ai/tests/discovery.spec.ts` drives the probe against local HTTP servers — a listing with and without disclosed capacities, a preserved deployment path, an absent credential, a configured route supplying its own where the draft has none and a typed key winning over it, a catalog route answering without resolving one at all, dropped rows, 401/403 versus a server fault, a non-listing and a non-JSON body, an unreachable endpoint, caller cancellation, an unsupported protocol, and the size ceiling in both its declared-length and streamed forms. `packages/client/connection/tests/node-half.host.spec.ts` pins the `llm/discoverModels` `/api` carrier registration, while `packages/client/ui-settings-models/tests/provider-form.client.spec.tsx` verifies that the draft reaches the Remote whole, absent fields stay absent, and no settings namespace or credential is written before selection.

+ 1 - 1
.agents/notes/implemented/architecture/2026-08-04-draft-provider-endpoint-interrogation.zh.md

@@ -47,4 +47,4 @@ pi-ai 提供了 `createProvider({ fetchModels })` 加上 `Models.refresh()` 与
 
 ## Testing
 
-`packages/llm/llm/tests/topology.spec.ts` 覆盖注册表:每个 namespace 一份、随 fiber dispose(资源释放)、丢弃重复与不可用 id 且不凭空补容量的归一化,以及 `NO_DISCOVERY`/`INVALID_DISCOVERY` 两种拒绝。`packages/llm/llm-pi-ai/tests/discovery.spec.ts` 针对本地 HTTP 服务器驱动探测——含与不含公布容量的列表、被保留的部署路径、无凭据、草稿没带密钥时已配置路由自行取用凭据且键入的密钥压过它、catalog 路由完全不解析凭据即作答、被丢弃的行、401/403 与服务器故障之别、非列表与非 JSON 响应、不可达端点、调用方取消、不支持的协议,以及尺寸上限的「声明长度」与「流式」两种形态。`packages/api/settings-controller/tests/` 在真实 proxy 上覆盖该 RPC:草稿完整抵达其 namespace、缺席字段保持缺席、没有 namespace 或凭据被写入,以及失败以 `model-discovery-failed` 呈现且序列化后的错误里不含凭据。
+`packages/llm/llm/tests/topology.spec.ts` 覆盖注册表:每个 namespace 一份、随 fiber dispose(资源释放)、丢弃重复与不可用 id 且不凭空补容量的归一化、`NO_DISCOVERY`/`INVALID_DISCOVERY` 两种拒绝,以及 `model-discovery-failed` Remote 映射。`packages/llm/llm-pi-ai/tests/discovery.spec.ts` 针对本地 HTTP 服务器驱动探测——含与不含公布容量的列表、被保留的部署路径、无凭据、草稿没带密钥时已配置路由自行取用凭据且键入的密钥压过它、catalog 路由完全不解析凭据即作答、被丢弃的行、401/403 与服务器故障之别、非列表与非 JSON 响应、不可达端点、调用方取消、不支持的协议,以及尺寸上限的「声明长度」与「流式」两种形态。`packages/client/connection/tests/node-half.host.spec.ts` 固定 `llm/discoverModels` 的 `/api` 承载注册,`packages/client/ui-settings-models/tests/provider-form.client.spec.tsx` 则验证草稿完整抵达 Remote、缺席字段保持缺席,以及选择前没有 settings namespace 或凭据被写入。

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.i18n.yaml

@@ -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/architecture/2026-08-06-subagent-list-identity-projection.md
-2026-08-06-subagent-list-identity-projection.md: d48d6f869b49861ac54d19c9efc9bda42cf8e575
-2026-08-06-subagent-list-identity-projection.zh.md: a5765a98bbd6aa5aa8552fa80a0e253d488b790e
+2026-08-06-subagent-list-identity-projection.md: 5124d5df9edafe5c11a68aff7a0dd2f7929bd020
+2026-08-06-subagent-list-identity-projection.zh.md: 77bf34790f7dfe93fdd8f725b707ecdc4ab4cf03

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 23 - 19
.agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.md


+ 29 - 23
.agents/notes/implemented/architecture/2026-08-06-subagent-list-identity-projection.zh.md

@@ -14,25 +14,25 @@ Status: implemented
 
 ## 决策
 
-mode 与 label 由新的 `subagent` projection unit(纯身份两臂)折叠,unit 是折叠规则的唯一权威;`listChildren` 不再依赖 session-query——枚举是 subagent 自管的 live-preferred 合并,取值走三级「算完即止」阶梯:live child 同步读注册表的既有水位缓存(零日志读);cold child 先问可选的 `sessionProjectionCache` checkpoint,取到过 seq 门的身份即定值;否则一次 `persistence.inspect` 整读加 `registry.restore` 折叠。无索引、不自建缓存、无回写。
+mode 与 label 由新的 `subagent` projection unit(纯身份两臂)折叠,unit 是折叠规则的唯一权威;`listChildren` 不再依赖 session-query——枚举是 subagent 自管的 live-preferred 合并,取值走三级「算完即止」阶梯:live child 同步读注册表的既有水位缓存(零日志读);cold child 先问可选的 `sessionProjectionCache` checkpoint,取到过 seq 门的身份即定值;否则一次 `persistence.inspect` 整读加经注册的 `subagent` unit 折叠。无索引、不自建缓存、无回写。
 
 消除逐 child 扫描的出路有三类:把 mode/label 提升进 header(写路承担);为投影建持久派生(checkpoint 阶梯,或随查询索引重建落值、读端对账);读时现算(live 走水位缓存,cold 一次整读)。本记录取第三条。「值随查询索引落库」已整体退役:查询基础设施被迫认识领域词汇,而唯一消费方读时现算即可满足——live child 的零读由 session-projection 既有水位缓存白拿,cold child 的一次整读被「算完即止」显式接受。前两条与退役理由详见考虑过的替代方案一节。
 
 要点:
 
 - **subagent 列表不依赖 session-query**:枚举由 subagent 自管的 live-preferred 合并完成,mode/label 经 `ctx.sessionProjections` 取值;没有 query backend 的部署照常列表。
-- **取值三级「算完即止」阶梯**:live child 读 `sessionProjections.snapshot()`(注册表既有水位缓存,零日志读);cold child 先读可选 `sessionProjectionCache.cachedSnapshot(header)`,values 含非 null 且过 seq 门(`seq >= seedLength ?? 0`)的 `subagent` 身份即直接用;否则一次 `persistence.inspect` 整读加 `registry.restore({}, events, 0)` 折叠;再没有就没有——不自建缓存、无回写、无索引。
-- **`subagent` projection unit 是折叠规则唯一权威**:live snapshot、cold restore、GUI history 的 detached 折叠全部经 registry 计算,不存在第二份描述符解释逻辑。
+- **取值三级「算完即止」阶梯**:live child 读 `sessionProjections.snapshot(session, ['subagent'])`(注册表既有水位缓存,零日志读);cold child 先读可选 `sessionProjectionCache.cachedSnapshot(header, ['subagent'])`,非 null 身份通过 seq 门(`seq >= seedLength ?? 0`)即直接使用;否则执行一次完整 Session 观察,再经注册的 `subagent` unit 折叠;再没有就没有——不自建缓存、无回写、无索引。
+- **`subagent` projection unit 是折叠规则唯一权威**:live 与 cold 快照都运行同一份已注册 unit,不存在第二份描述符解释逻辑。
 - **header、描述符(v2)、session-persistence、session-projection(-cache)、session-query(-sqlite) 全部零改动**;存量数据第一次被列表时一次 `inspect` 现算获得精确值,无 unknown 降级态、无迁移。
 
 与既有记录的关系:
 
 - 本记录取代 [durable-subagent-catalog](../feature/2026-07-22-durable-subagent-catalog-and-list-agents.zh.md) 中列表读路径的两项设计:经 `sessionQuery.traceSession` 枚举,与逐 child 读取描述符事件(`listEvents` 加精确 `readEvent` 双读、就地诊断分类)。diagnostic 行语义保留,分类改由列表按投影值缺席与 activity 派生;描述符事件仍是 mode/label 的唯一持久权威与折叠输入,恢复鉴权与激活约定不动。属部分取代,两记录保持交叉链接。
-- [session-projection RFC](../../proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md) 的 registry 约定(`ProjectionDefinition`、`snapshot`、`restore`)零改动,本记录只为其新增 `subagent` 身份 unit 一个注册项,并成为 snapshot(live)与 restore(cold)两处既有读法的又一消费实例——GUI history 的冷读已是同款。折叠规则只在 registry 注册一份;任何消费面都经 registry 计算,不存在第二份折叠逻辑。
+- [session-projection RFC](../../proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md) 是 registry 约定的权威,其后由 [state-and-client-views 记录](2026-08-19-session-projection-state-and-client-views.zh.md)拆分为 host 状态与客户端视图;本记录新增客户端可见的 `subagent` 身份 unit,并经 live 与 cold 快照消费它。折叠规则只在 registry 注册一份;任何消费面都经这一份已注册 unit 计算,不存在第二份折叠逻辑。
 
 ### `subagent` projection unit
 
-挂在现有 `subagentTiming` 旁([projection.ts](../../../../packages/subagent/subagent/src/projection.ts)、[projection-types.ts](../../../../packages/subagent/subagent/src/projection-types.ts)),key 为 `subagent`:
+挂在现有 `subagentTiming` 旁([projection.ts](../../../../packages/subagent/subagent/src/projection.ts)、[projection-types.ts](../../../../packages/subagent/subagent/src/projection-types.ts)),key 为 `subagent`。两个 unit 都提供客户端 wire view;身份 unit 在 host 状态中保留可选的包装值,并把缺席映射为客户端哨兵:
 
 ```ts ignore-check
 export type SubagentIdentityProjection =
@@ -40,15 +40,19 @@ export type SubagentIdentityProjection =
   | { mode: 'continuable'; label: string; seq: number }
 
 declare module '@deepseek-ai/dsh-session-projection/types' {
+  interface SessionProjectionStateMap {
+    subagent: { identity?: SubagentIdentityProjection }
+  }
   interface SessionProjectionMap {
+    subagentTiming: SubagentTimingProjection
     subagent: SubagentIdentityProjection | null
   }
 }
 ```
 
-- 投影是纯身份,**projection 体系不做失败通道**:unit 永不抛错;载荷损坏、版本不认识与整日志没有描述符一样,折叠结果是**可序列化的 null 哨兵**——map 条目为 `SubagentIdentityProjection | null`,非可选、非 undefined/缺 key。理由:registry 的 onChanged 推送经 JSON 序列化,undefined 字段被 stringify 丢弃,客户端帧校验拒收,消费方存储的旧身份将永不更新;null 完好过帧,消费方以哨兵替换旧身份。判定纪律:消费面把 null 与 undefined(仅 JSON 边界丢 key 可产生)一律视为无值。「算出来没有」如何呈现是消费方自己的事(见下文 `listChildren` 四态映射)。
+- 投影是纯身份,**projection 体系不做失败通道**:unit 永不抛错;载荷损坏、版本不认识与整日志没有描述符一样。host checkpoint 状态使用可序列化的包装 `{ identity?: SubagentIdentityProjection }`,缺席为 `{}`;客户端 view 则是非可选的 `SubagentIdentityProjection | null` 条目。`null` 完好通过 JSON,因此推送 reset 会替换旧身份,而不会被 stringify 丢掉。判定纪律:消费面把 null 与客户端 key 缺席一律视为无值。「算出来没有」如何呈现是消费方自己的事(见下文 `listChildren` 四态映射)。
 - label 强度由描述符 schema 决定:continuable 的 label 解析强制必有,one-shot 的本就可选;mode/label 判别与下文 child 行的强约定完全一致(行不携带 `seq`——它是投影内部的 own-suffix 证明)。
-- 身份携带 `seq`:折出该身份的 `subagent/descriptor` 事件 seq,两臂必有、null 哨兵无——`seq >= header.seedLength ?? 0` 证明身份折叠自 child 自身后缀,而非 fork 种子回放的祖先描述符。state 增 `seq` 使 unit `stateVersion` 升至 2,既存 checkpoint 行按 registry 约定版本失配失效、落权威重折。
+- 身份携带 `seq`:折出该身份的 `subagent/descriptor` 事件 seq,两臂必有、null 哨兵无——`seq >= header.seedLength ?? 0` 证明身份折叠自 child 自身后缀,而非 fork 种子回放的祖先描述符。unit 把包装状态中校验后的身份映射为客户端 wire view,并与其他 unit 一律检查点化(`persist` 选项已删除);`stateVersion` 为 2,在增加 `seq` 时升版。更早的 checkpoint 行按 registry 约定版本失配失效、落权威重折。
 - 折叠规则:`subagent/descriptor` last-wins,与 `subagentTiming` 同一条 descriptor-reset 纪律——fork 前缀里的祖先描述符被自身描述符覆盖。损坏或版本不认识的载荷同样 last-wins:重置为 null 哨兵而非保留先前身份,健康祖先的 fork 不会继承自身描述符立不住的身份。
 
 ### 枚举:subagent 自管 live-preferred 合并
@@ -68,12 +72,12 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
 
 | 级 | 读法 | 成本 |
 | --- | --- | --- |
-| 1:live child | `ctx.sessionProjections.snapshot(session).values.subagent` | 零日志读——注册表既有水位缓存,同步取值 |
-| 2:cold child,cache 命中 | 可选 `sessionProjectionCache.cachedSnapshot(header)`,values 含非 null 的 `subagent` 身份且 `identity.seq >= header.seedLength ?? 0` 才直接用——own descriptor 一经追加不可变,seq 门证明该值折叠自 child 自身后缀,无视行水位 | 零日志读 |
-| 3:cold child,兜底 | `persistence.inspect(id)` 整读 + `registry.restore({}, events, 0).snapshot.values.subagent` | 每次列表一次整读现算 |
+| 1:live child | `ctx.sessionProjections.snapshot(session, ['subagent'])` | 零日志读——注册表既有水位缓存,同步取值 |
+| 2:cold child,cache 命中 | 可选 `sessionProjectionCache.cachedSnapshot(header, ['subagent'])`,非 null 身份满足 `identity.seq >= header.seedLength ?? 0` 才直接使用——own descriptor 一经追加不可变,seq 门证明该值折叠自 child 自身后缀,无视行水位 | 零日志读 |
+| 3:cold child,兜底 | `persistence.inspect(id)` 整读 + 经注册的 `subagent` unit 折叠 | 每次列表一次整读现算 |
 
-- 错误约定:`ctx.sessionProjections` 未挂载是配置错误,`listChildren` 在枚举前无条件检查并以 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` 响亮失败——零 children 的部署同样确定失败,不因列表恰好为空而掩盖配置问题。会话存储同理:`ctx.get('sessions')`(严格全局读取,不走调用方作用域的属性代理)缺席以 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE` 失败。两码的 wire 映射有别:apiproxy 只为 `PROJECTIONS_UNAVAILABLE` 设专门 wire 脸,`SESSION_STORE_UNAVAILABLE` 走通用 internal 兜底——apiproxy 组合自身就 inject `sessions`,该错误在其部署不可达,专门映射违反 need 原则。`SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 已随 session-query 依赖删除。
-- cache 是纯可选加速层:服务缺席判空跳过——无错误码、不进配置校验(与 `sessionProjections` 的响亮约定相对)。第二级任何抛错(包括缓存内任一 unit 行中毒使 `viewCheckpoint` 引爆)静默落第三级——缓存是派生数据,其故障不产生 `corrupt` 判决,终审归权威重折;checkpoint 切面早于描述符的行,`subagent` key 天然缺席,自动落底,无特判;行里的 null 哨兵同样不作数——一律落第三级,由权威重折裁决。创建窗口内的 count/interval checkpoint 可能把 fork 种子回放的祖先身份落进行——祖先 seq 落在 seed 区间,被 seq 门拒绝,同样落第三级裁决。
+- 错误约定:`sessionProjections` 是必需注入——`SubagentRuntime` 在 inject 集里声明它,没有 registry 的部署根本无法激活服务(与 loop),`listChildren` 不可达,而不是供出降级行([mandatory-seam 记录](2026-08-19-session-projection-mandatory-seam.zh.md));响亮运行时检查与 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` 随之删除。会话存储保留显式姿态:`ctx.get('sessions')`(严格全局读取,不走调用方作用域的属性代理)缺席以 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE` 失败。apiproxy 为 `PROJECTIONS_UNAVAILABLE` 设的专门 wire 脸随码删除;`SESSION_STORE_UNAVAILABLE` 走通用 internal 兜底——apiproxy 组合自身就 inject `sessions`,该错误在其部署不可达,专门映射违反 need 原则。`SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 已随 session-query 依赖删除。
+- cache 是纯可选加速层:服务缺席判空跳过——无错误码、不进配置校验(与 `sessionProjections` 的必需注入相对)。第二级任何抛错(包括缓存内任一 unit 行中毒使 `viewCheckpoint` 引爆)静默落第三级——缓存是派生数据,其故障不产生 `corrupt` 判决,终审归权威重折;checkpoint 切面早于描述符的行,`subagent` key 天然缺席,自动落底,无特判;行里的 null 哨兵同样不作数——一律落第三级,由权威重折裁决。创建窗口内的 count/interval checkpoint 可能把 fork 种子回放的祖先身份落进行——祖先 seq 落在 seed 区间,被 seq 门拒绝,同样落第三级裁决。
 - per-child 隔离:单 child 的 cold 整读失败只使该行成为 `unavailable` diagnostic,下次列表自然重试,不影响 sibling(见四态映射)。
 - 冷路径的生命周期见证:preparation 的结果必须仍指向枚举时的那个生命周期——见证字段集与旧 SOURCE_CONFLICT 检查同款七字段(version、id、createdAt、cwd、parentSession、seedLength、delegationDepth);同 id 删除后重新发布的会话对旧 parent 的目录降级为 `corrupt` 行,不外漏新 owner 的 child。
 - 冷读并发以常数 4 有界——它约束的是本地介质的一次只读扫描而非部署行为;出现联网 persistence backend 时提升为验证过的 `Config` 字段。
@@ -119,7 +123,7 @@ export type SubagentListEntry =
 
 - `unsupported` 不再被产出:类型与 wire 枚举按「数据结构保持现状」留存该成员,本记录留档其为不再产出。
 - descriptor-less 定局残骸从旧实现的 omit 归入 `corrupt` diagnostic——库里的坏、死子会话可见,不静默消失,这正是保留 diagnostic 的原始动机。
-- 任一注册 unit 的 fold/schema 在该 child 日志上抛错,同样收纳为该 child 的 diagnostic 行,reason `corrupt`——确定性数据故障,对齐旧实现 `SESSION_QUERY_CORRUPT_SESSION`→`corrupt` 的映射语义;live 与 cold 同待遇,逐 child 隔离,sibling 与列表本身不受影响。它与「无值 + running → omit」正交:创建窗口是「尚无数据」,fold 抛错是「数据坏了」——running 的中毒 child 也出 `corrupt` 行而非 omit。
+- 列表只选择 `subagent` wire unit,且该折叠永不抛错:描述符损坏或版本不认识折为 null 哨兵,由四态映射收纳为该 child 的 `corrupt` 行(确定性数据故障,对齐旧实现 `SESSION_QUERY_CORRUPT_SESSION`→`corrupt` 的映射语义)。live 与 cold 同待遇,逐 child 隔离,sibling 与列表本身不受影响。它与「无值 + running → omit」正交:创建窗口是「尚无数据」,折为无值是「数据坏了」——running 的中毒 child 也出 `corrupt` 行而非 omit。
 
 已知边界偏差(有意接受,随本记录留档):
 
@@ -130,15 +134,15 @@ export type SubagentListEntry =
 - 未知 parent,旧实现经 session-query 抛 not-found(「parent session … was not found」);现自管合并对不存在的 parent 得到空子集,枚举返回空列表,wire 上后续操作落到 child 级 subagent-not-found——语义与文案的静默变化,显式接受。
 - rung 2 的更晚事件窗口:cache 行恰在首个自有描述符之后落盘,日志随后追加第二个自有描述符(或 malformed 载荷置 null 哨兵),且进程在下一次 checkpoint 前崩溃——此后冷列表的 rung 2 凭 seq≥seedLength 门持续供出行内旧身份(第一个自有描述符的值),与权威重折(last-wins 第二个)分歧,且 rung 2 命中期间不触发重折、无从察觉。边界三条:①前提是同一 child 出现第二个自有描述符,违反建档提供方「恰追加一次」约定,属损坏类数据,与多描述符偏差同族同源;②需「损坏 + 崩溃错过 checkpoint(turn/end 与 disposal 两个 mandatory 点及 count/interval 节流点全部未及)」双条件同时成立;③健康 child(恰一自有描述符)不受影响——seq 门放行的正是唯一真身份。自愈条件:该 child 任一次 live 运行(turn/end mandatory checkpoint)或任何触发 cache.write 的时点,都会以新 fold 整行覆写(whole-record replace),rung 2 随即供正;权威路径(rung 3 重折、live snapshot、resume 折叠)自始正确,分歧只存在于持续冷、行未再更新期间的列表读。机制修法不采:gate 对账需知日志末端 seq,冷路径零读不可得;cache 行携 revision 是 opaque token,无法比较且跨域改 schema——按「cache 永不为权威」总纲归档为接受项。
 
-消费面:wire、tool、GUI 的 diagnostic 处理**全部保持原状零改动**(`list_agents` 的 description 与 output schema 未动;该插件仅加载要求收窄——inject 去掉 `sessionQuery`)。行为上动的只有 apiproxy:路由段的 `hasSubagentDescriptor()` 扫描已删除,`hasSubagentOwner` 只看 `header.origin`——pre-#1569 的无 `origin` 存量不再被认作 subagent 属主,其本就不进目录,pre-release 立场接受;`subagents.history` 与 `session.history` 同源对齐——live child 用内存事件与注册表水位快照,cold child 用 `inspectServable` 直读持久化并 detached 折叠,不经查询服务,SESSION_QUERY_* 错误臂随之退役,wire 形状不变(`history` 的 JSDoc 措辞改为 live 内存快照/cold 持久日志双臂)。
+消费面:wire、tool、GUI 的 diagnostic 处理**全部保持原状零改动**(`list_agents` 的 description 与 output schema 未动;该插件的加载要求变化——inject 去掉 `sessionQuery`、新增必需注入 `sessionProjections`)。行为上动的只有 apiproxy:路由段的 `hasSubagentDescriptor()` 扫描已删除,`hasSubagentOwner` 只看 `header.origin`——pre-#1569 的无 `origin` 存量不再被认作 subagent 属主,其本就不进目录,pre-release 立场接受;`subagents.history` 与 `session.history` 同源对齐——live child 用内存事件与注册表水位快照,cold child 用 `inspectServable` 直读持久化并 detached 折叠,不经查询服务,SESSION_QUERY_* 错误臂随之退役,wire 形状不变(`history` 的 JSDoc 措辞改为 live 内存快照/cold 持久日志双臂)。
 
 ### 改动落点
 
 | 区域 | 文件 | 改动 |
 | --- | --- | --- |
-| subagent | projection.ts、projection-types.ts、index.ts | 新 `subagent` unit 与注册 |
-| subagent | list-children.ts 及类型 | 重写为自管枚举 + 投影阶梯四态映射;删 session-query 依赖、逐 child 事件读取与就地分类机器;错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 换 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE`;新增可选依赖 dsh-session-projection-cache(纯加速读取,缺席跳过) |
-| host/apiproxy | api-proxy.ts | 删 `hasSubagentDescriptor`,属主判定只看 `header.origin`;`subagents.history` 与 `session.history` 同源——live 用内存事件与注册表水位快照,cold 用 `inspectServable` 直读持久化并 detached 折叠,不经查询服务,SESSION_QUERY_* 错误臂随之退役 |
+| subagent | projection.ts、projection-types.ts、index.ts | 新客户端可见 `subagent` unit 与注册 |
+| subagent | list-children.ts 及类型 | 重写为自管枚举 + 投影阶梯四态映射;删 session-query 依赖、逐 child 事件读取与就地分类机器;错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 删除,`sessionProjections` 转为必需注入(不再存在投影错误码);新增可选依赖 dsh-session-projection-cache(纯加速读取,缺席跳过) |
+| host/apiproxy | api-proxy.ts | 删 `hasSubagentDescriptor`,属主判定只看 `header.origin`;`subagents.history` 与 `session.history` 同源——live 用内存事件与注册表水位快照,cold 用 `inspectServable` 直读持久化并 detached 折叠,不经查询服务,SESSION_QUERY_* 错误臂与 `PROJECTIONS_UNAVAILABLE` 专门 wire 脸随之退役 |
 | tool | tool-subagent-control/list-agents.ts | 加载要求收窄(inject 去 `sessionQuery`);model-visible schema、描述与渲染零改动 |
 | wire/client | api/subagents.ts、runtime sessions/service.ts、GUI | 类型、行形状与 diagnostic 处理**零改动**;api/subagents.ts 仅 `history` 的 JSDoc 措辞改为双臂 |
 | core/session、session-persistence、session-projection(-cache)、session-query(-sqlite) | — | **零改动** |
@@ -147,7 +151,7 @@ export type SubagentListEntry =
 
 **mode/label 进 SessionHeader。** 零读保证最强——列表只看 header 就能成行。但 header 形状变更传导两个 persistence backend 与 header 兼容检查;SQLite 存量直接拒收,JSONL 存量只能 unknown 降级或 backfill。读时现算对存量的答案是「第一次列表一次 `inspect` 现算」,不碰持久格式。
 
-**projection-cache 阶梯(`cachedSnapshot ?? coldSnapshot` 加 fail-soft 写回)。** 机制成立——session-projection-cache 的 checkpoint 阶梯本就为冷读设计。但 checkpoint 写回是一套由列表驱动的派生数据持久化与失效编排(floor/identity/putSoft);被否的是这套编排作为主机制。定稿的第三级阶梯后来以只读方式机会性复用该缓存作第二级——无写回、无编排、缺席即跳过。
+**projection-cache 阶梯(`cachedSnapshot ?? cold fold` 加 fail-soft 写回)。** 机制成立——session-projection-cache 的 checkpoint 阶梯本就为冷读设计。但 checkpoint 写回是一套由列表驱动的派生数据持久化与失效编排(floor/identity/putSoft);被否的是这套编排作为主机制。定稿的第三级阶梯后来以只读方式机会性复用该缓存作第二级——无写回、无编排、缺席即跳过。
 
 **给 persistence 加有界读原语抢救存量。** 为一次性问题新开 persistence 原语;被读时 `inspect` 整读取代——存量第一次被列表时的整读就是取值本身。
 
@@ -159,19 +163,19 @@ export type SubagentListEntry =
 
 **值随 query 索引 preparation 落库。** 投影值在 sqlite backend 的对账重建里折叠落进 session 索引行,读稳态零日志:`projectionsFor` 批量读面、行值随 `(key → stateVersion)` 注册集存储的失效对账与 SCHEMA bump。整体退役:方向反了——查询基础设施被迫认识领域词汇(投影列、注册集对账),而唯一消费方 subagent 列表读时现算即可满足;消费方归零后,这套派生持久化没有存在理由。`SESSION_QUERY_PROJECTIONS_UNAVAILABLE` 随读面一并删除。
 
-**subagent 手工 parse 加进程 memo 加创建播种。** 为摘除 session-query 依赖,由 subagent 包自己解析描述符事件、以进程内 memo 避免重复整读、创建时播种初值。被已交付的阶梯取代:live 走 `sessionProjections` 水位缓存、cold 走 `registry.restore`,复用 registry 这一份折叠权威,不再出现第二份描述符解释逻辑,也不引入进程态缓存与播种时序。
+**subagent 手工 parse 加进程 memo 加创建播种。** 为摘除 session-query 依赖,由 subagent 包自己解析描述符事件、以进程内 memo 避免重复整读、创建时播种初值。被已交付的阶梯取代:live 走 `sessionProjections` 水位缓存、cold 走注册 unit 的折叠,复用 registry 这一份折叠权威,不再出现第二份描述符解释逻辑,也不引入进程态缓存与播种时序。
 
 **session-query 输出面 DeepReadonly(读路径改造实验)。** 公开查询输出深只读化,以在类型层面钉死不可变借用。实证否决:3 处 TS2589(类型实例化过深)加 17 处数组位传染(消费方数组方法与展开处被迫跟改);深层不可变由 core/session 的运行时深冻结保证,该读路径改造未纳入本记录。
 
 ## 验证
 
-`packages/subagent/subagent/tests/list-children.spec.ts` 重写为本约定:无 persistence、query 服务与继续运行时的 live-only 列表;registry 缺席时零 children 也响亮报 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE`;live child 全程零 `inspect`、cold child 每次列表恰一次;多描述符 last-wins 取末者;损坏载荷与未知版本折为 `corrupt`;冷读失败映射 `unavailable` 且下次列表重试;fork seed 里的祖先描述符按该身份成行(偏差一钉住);普通 fork 与无 subagent origin 的后代不入列也不计入 `hasChildren`;`createdAt`→id 排序;提供方未挂载不影响列表;压缩与未压缩孪生一致;预中止、持久化列表与冷读取消三例归一 `CANCELLED`;空列表与稳定错误码。敌意 unit 双路探针(`apply` 惰性置毒、`view` 引爆)证明任一注册 unit 在该 child 日志上的 fold/schema 抛错,在 live 与 cold 两条取值路径上都收纳为该 child 的 `corrupt` 行,sibling 与列表本身不受影响。第二级例:own-seq 身份直用零 `inspect`、fork 种子祖先身份(seq 落在 seed 区间)被门拒绝落底、行内无身份(null 哨兵或 key 缺席)落底、cache 服务缺席落底、缓存行中毒静默落底重折;冷路径 lifecycle 篡改按见证七字段逐一(`it.each`)降级为 `corrupt`。`tool-subagent-control` 的 list-agents 测试随加载要求收窄更新;`optional-session-query.spec.ts` 随依赖消失删除;既有无密钥快照(`subagent-list-agents` 等)零变化,钉住健康路径的 wire 与 model-visible 面不变;归属方本地的 `apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts` 钉住四态映射的诊断分类——descriptor-less 定局残骸成 `corrupt` 行等模型可见变化。
+`packages/subagent/subagent/tests/list-children.spec.ts` 重写为本约定:无 persistence、query 服务与继续运行时的 live-only 列表;registry 缺席时服务根本不激活(mandatory seam——`setup` 变体断言 `ctx.get('subagents')` 保持 undefined);live child 全程零 `inspect`、cold child 每次列表恰一次;多描述符 last-wins 取末者;损坏载荷与未知版本折为 `corrupt`;冷读失败映射 `unavailable` 且下次列表重试;fork seed 里的祖先描述符按该身份成行(偏差一钉住);普通 fork 与无 subagent origin 的后代不入列也不计入 `hasChildren`;`createdAt`→id 排序;提供方未挂载不影响列表;压缩与未压缩孪生一致;预中止、持久化列表与冷读取消三例归一 `CANCELLED`;空列表与稳定错误码(存储缺席时 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`)。第二级例:own-seq 身份直用零 `inspect`、fork 种子祖先身份(seq 落在 seed 区间)被门拒绝落底、行内无身份(null 哨兵或 key 缺席)落底、cache 服务缺席落底、缓存行中毒静默落底重折;冷路径 lifecycle 篡改按见证七字段逐一(`it.each`)降级为 `corrupt`。`tool-subagent-control` 的 list-agents 测试随加载要求收窄更新;`optional-session-query.spec.ts` 随依赖消失删除;既有无密钥快照(`subagent-list-agents` 等)零变化,钉住健康路径的 wire 与 model-visible 面不变;新增无密钥快照 `subagent-diagnostic`(examples/headless-agent)钉住四态映射的诊断分类——descriptor-less 定局残骸成 `corrupt` 行等模型可见变化。
 
 ## 后果
 
 - live child 的列表全程零日志读;cold child 在 cache 未挂载或未命中时每次列表一次 `inspect` 整读,成本与其 transcript 大小成正比、随列表频率重复——定案「算完即止」,不自建缓存、不回写,同 id 短期重复整读可命中准备阶段 LRU 但列表不依赖它。
-- subagent 列表不再要求 query backend:纯 live 与无 persistence 的部署都能列表;`SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 消失,`list_agents` 插件加载不再要求 `sessionQuery`。
-- 身份解释只存在于 registry 注册的一份 unit:列表三级阶梯与 GUI history 冷读走的都是 registry 与 cache 的既有读法(snapshot、cachedSnapshot、restore),不存在旁路折叠;若未来某消费面绕开 registry 手写折叠,各读面的值将漂移——这是本设计要求维持的纪律,不是机制保证。
+- subagent 列表不再要求 query backend:纯 live 与无 persistence 的部署都能列表;`SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 消失,`list_agents` 插件加载不再要求 `sessionQuery`,而 `sessionProjections` 转为 `SubagentRuntime` 的必需注入——没有投影 registry 的部署根本不会激活服务(mandatory seam)。
+- 身份解释只存在于 registry 注册的一份 unit:列表三级阶梯与 GUI history 冷读使用其 live、cached 或 observed wire 快照,不存在手写旁路折叠;若未来某消费面绕开该 unit 手写折叠,各读面的值将漂移——这是本设计要求维持的纪律,不是机制保证。
 - per-child 隔离回归:单 child 冷读失败只损失该行,healthy sibling 不受影响;persistence 列表失败仍使整次枚举失败。
 - 诊断与枚举语义留下六处边界偏差(stillborn fork 祖先身份误现、多描述符取末者、header 冲突不再被察觉、损坏源读失败由 `corrupt` 转 `unavailable`、未知 parent 由 not-found 改为空列表、rung 2 更晚事件窗口),完整语义见已知边界偏差清单;前四处为残骸级数据的展示或分类偏差,未知 parent 一处是查询语义的静默变化,rung 2 窗口一处是损坏加崩溃双条件下可自愈的缓存供值分歧;恢复鉴权均不受影响,显式接受。
 - pre-#1569 的无 `origin` 存量不再被认作 subagent 属主;其本就不进目录,pre-release 无兼容承诺。
@@ -179,6 +183,8 @@ export type SubagentListEntry =
 ## 相关
 
 - [durable-subagent-catalog 与 list_agents](../feature/2026-07-22-durable-subagent-catalog-and-list-agents.zh.md)——被本记录部分取代:描述符仍是 mode/label 的持久权威与折叠输入,列表的枚举与取值改为自管合并加投影阶梯。
-- [session projections 与命令生命周期日志](../../proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md)——registry 约定的权威;本记录为其新增 `subagent` 身份 unit,并成为 snapshot/restore 两处既有读法的消费实例。
+- [session projections 与命令生命周期日志](../../proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md)——registry 约定的权威;本记录为其新增 `subagent` 身份 unit,并消费其 live 与 cold wire 快照。
+- [session projection 状态与客户端视图](2026-08-19-session-projection-state-and-client-views.zh.md)——state/client 拆分;`subagent` 与 `subagentTiming` 都提供客户端 wire view。
+- [session projections 作为必需接缝](2026-08-19-session-projection-mandatory-seam.zh.md)——`sessionProjections` 转为必需注入;列表的错误约定随其变化(registry 缺席是激活期失败,投影错误码删除)。
 - [web subagent conversations](../feature/2026-07-27-web-subagent-conversations.zh.md)——`SessionHeader.origin` 的出处(#1569),身份判定去日志化的前半步;其 history 冷读(inspect 前缀加 registry 折叠)是本记录取值阶梯的同款先例。
 - [发布前可复用的 Session 准备阶段](2026-08-05-session-preparation.zh.md)——`inspect()` 冷读与 LRU 复用;cold child 整读的成本模型建立其上。

+ 6 - 0
.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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/architecture/2026-08-19-session-projection-mandatory-seam.md
+2026-08-19-session-projection-mandatory-seam.md: 7d67e0222d8ddead708d4ac62c30a99164f69d71
+2026-08-19-session-projection-mandatory-seam.zh.md: 40c05cec723290a52dc400015bb17009886c933d

+ 35 - 0
.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md

@@ -0,0 +1,35 @@
+# Agent Note: Session projections as a required reader seam
+
+Status: implemented
+
+English | [中文](2026-08-19-session-projection-mandatory-seam.zh.md)
+
+## Problem
+
+An optional projection registry lets a plugin whose host behavior reads projected state activate without that state. Unless the reader rejects the missing registry or key, the host behavior or subagent catalog fields can silently disappear. Batch-only reads also materialize every client view when a consumer needs one host value. Some contributor sites intentionally retain optional `ctx.inject` registration, so their readers need an explicit missing-state rule.
+
+## Decision
+
+This decision builds on the split between host projection state and client views in [Session projection state and client views](2026-08-19-session-projection-state-and-client-views.md).
+
+Every host reader treats the projection registry and its required key as mandatory state. A plugin either declares `sessionProjections` as a required injection or resolves the registry and key explicitly and throws on the first dependent access. Official compositions mount the registry before those plugins. `ApiProxyService` follows the required-injection form; the lower-level `createApiProxy` factory remains tolerant for isolated tests and diagnostics.
+
+A domain contributor may register its unit through `ctx.inject(['sessionProjections'], ...)`. Optional registration controls the child lifecycle only; it does not authorize a reader to substitute a default when the registry or key is absent.
+
+The registry provides `stateOf(session, key)` for one typed host state and keeps `snapshot()` for batch carriers. Client views contain only consumed fields; host readers use `stateOf` for richer state.
+
+`onChanged` publishes client-visible value changes only. Unit registration and removal remain effect-scoped registry lifecycle; `register()` returns the exact Cordis disposer so a composite domain owner can finish cleanup against projected state before removing its unit. Registration changes do not create a second Host event stream or client tombstone protocol. A later authoritative history or list baseline reflects the active key set.
+
+## Alternatives considered
+
+- **Default missing projected state.** This preserves more partial compositions but makes missing host state indistinguishable from a valid empty value. Rejected because official profiles mount the registry and configuration errors must fail explicitly.
+- **Require every contributor at activation.** This makes the key set uniform but unnecessarily couples contribution lifecycle to service activation. Explicit first-access failure preserves the optional registration form without allowing silent degradation.
+- **Use `snapshot()` for every read.** This keeps one method but computes unrelated wire views and encourages consumers to depend on batch transport data for host logic. Rejected in favor of typed single-key state reads.
+- **Send full host values to clients.** This avoids separate view types but exposes provenance and policy knobs that no client consumes. Rejected in favor of explicit cropped views.
+- **Broadcast registry additions and removals across Host and mux streams.** The streams have no shared ordering, so clients need tombstones, buffered frames, and baseline retries to reconcile them. Rejected because plugin-key churn does not justify a second synchronization protocol.
+
+## Consequences
+
+- Missing projection composition fails either during activation or at the first dependent host access; it never degrades to a default value.
+- Host consumers avoid repeated whole-registry snapshots and log scans.
+- Wire payloads exclude host-only fields and per-key watermark wrappers; ordinary baselines communicate the active key set.

+ 35 - 0
.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md

@@ -0,0 +1,35 @@
+# Agent Note: 会话投影作为必需的读取 seam
+
+Status: implemented
+
+[English](2026-08-19-session-projection-mandatory-seam.md) | 中文
+
+## 问题
+
+可选的投影注册表会让 host 行为读取投影状态的插件在缺少该状态时仍然激活。除非读取方拒绝缺失的注册表或 key,否则 host 行为或 subagent 目录字段可能静默消失。只有批量读取也会在消费方只需要一个 host 值时物化每个客户端 view。部分贡献位置有意保留可选的 `ctx.inject` 注册形式,因此其读取方需要明确的缺失状态规则。
+
+## 决策
+
+本决策建立在[会话投影的 host 状态与客户端视图](2026-08-19-session-projection-state-and-client-views.zh.md)所定义的拆分之上。
+
+每个 host 读取方都把投影注册表及其所需 key 视为必需状态。插件要么把 `sessionProjections` 声明为必需注入,要么显式解析注册表与 key,并在第一次依赖它们的访问时抛错。正式组合在这些插件之前挂载注册表。`ApiProxyService` 采用必需注入形式;较低层的 `createApiProxy` factory 对隔离测试和诊断保持容错。
+
+领域贡献方可以通过 `ctx.inject(['sessionProjections'], ...)` 注册单元。可选注册只控制子功能生命周期;它并不允许读取方在注册表或 key 缺失时替换为默认值。
+
+注册表提供 `stateOf(session, key)` 来读取一个类型化 host 状态,并为批量 carrier 保留 `snapshot()`。客户端 view 只包含消费方使用的字段;host 读取方通过 `stateOf` 取得更丰富的状态。
+
+`onChanged` 只发布客户端可见值的变化。单元注册和移除仍是绑定 effect 的注册表生命周期;`register()` 返回 Cordis 的原始 disposer,使组合式领域 owner 可以先依据投影状态完成清理,再移除自身单元。注册变化不会创建第二条 Host 事件流或客户端 tombstone 协议。后续权威 history 或 list 基线会反映活跃 key 集。
+
+## 考虑过的替代方案
+
+- **为缺失的投影状态提供默认值。** 这会保留更多不完整组合,但缺失 host 状态将无法与合法空值区分。正式 profile 已挂载注册表,配置错误必须显式失败,因此否决。
+- **要求每个贡献方都在激活时强依赖。** 这会统一 key 集,却会让贡献生命周期与服务激活产生不必要的耦合。首次访问时显式失败既能保留可选注册形式,也不会允许静默降级。
+- **每次读取都使用 `snapshot()`。** 这只保留一个方法,但会计算无关 wire view,并鼓励消费方让 host 逻辑依赖批量传输数据。改用类型化单 key 状态读取。
+- **向客户端发送完整 host 值。** 这避免单独的 view 类型,但会暴露客户端不消费的来源信息和策略旋钮。改用显式裁剪的 view。
+- **跨 Host 和 mux stream 广播注册表新增和移除。** 两条 stream 没有共享顺序,客户端因此需要 tombstone、缓冲帧和基线重试来协调。插件 key 变化不值得引入第二套同步协议。
+
+## 后果
+
+- 缺少投影组合会在激活期间或第一次依赖它的 host 访问时失败,绝不会降级为默认值。
+- host 消费方避免重复的全注册表快照和日志扫描。
+- 协议负载排除 host 内部字段和逐 key 水位包装;普通基线会传达活跃 key 集。

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.i18n.yaml

@@ -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/architecture/2026-08-19-session-projection-state-and-client-views.md
-2026-08-19-session-projection-state-and-client-views.md: 14da0525b2cc838ff496d5902dd66ae6ab456af4
-2026-08-19-session-projection-state-and-client-views.zh.md: 3b1ed72bf240ebf43924826d42226ff301ad8fca
+2026-08-19-session-projection-state-and-client-views.md: e8b4c3c052ba3f172515a4d131b2f58f75d902d8
+2026-08-19-session-projection-state-and-client-views.zh.md: fb2488aeb6be00f981444a26baab2b5d9c8115ae

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.md

@@ -12,11 +12,11 @@ The projection registry persisted each unit's internal fold state without a runt
 
 `SessionProjectionStateMap` is the merge-extensible table for host fold states. Every `ProjectionDefinition` key belongs to this table and supplies a `stateSchema`; cached rows are validated before they seed a fold. `SessionProjectionMap` retains its existing meaning and name as the sole table of client-visible whole values, preserving existing client data structures such as `title: string | null`.
 
-A unit whose key also appears in `SessionProjectionMap` supplies `wire.viewSchema` and `wire.view`. Every unit's state is checkpointed — client-visible and host-only alike; the `persist` opt-in is gone, so no unit can silently skip the durable cache. Snapshot APIs return only `SessionProjectionMap`, so internal states cannot enter API payloads. Host code reads one current state through `stateOf(session, key)`; the returned reference is borrowed and must not be mutated.
+A unit whose key also appears in `SessionProjectionMap` supplies `wire.viewSchema` and `wire.view`. Every unit's state is checkpointed — client-visible and host-only alike; the `persist` opt-in is gone, so no unit can silently skip the durable cache. Snapshot APIs enumerate only definitions with `wire`, so omitting an audience flag cannot place internal state in an API payload. Host code reads one current state through `stateOf(session, key)`; the returned reference is borrowed and must not be mutated.
 
 ## Consequences
 
-Projection state and client values are independently typed and validated without introducing a second client DTO vocabulary. A unit may expose a compact or compatibility-preserving client value while retaining richer host state. Malformed cached state cannot seed `viewCheckpoint`; restore rejects malformed state and the cache's existing full-read fallback rebuilds it from the log. Host consumers can replace private log scans with the same incremental fold used by carriers.
+Projection state and client values are independently typed and validated without introducing a second client DTO vocabulary. A unit may expose a compact or compatibility-preserving client value while retaining richer host state. Malformed cached state cannot seed `viewCheckpoint`; restore rejects malformed state and the cache's existing full-read fallback rebuilds it from the log. Host consumers can replace private log scans with the same incremental fold used by carriers, while carrier snapshots cannot enumerate host-only state.
 
 The original [session-projection proposal](../../proposed/architecture/2026-07-27-session-projection-and-command-log.md) now records this split. The earlier [subagent identity projection](2026-08-06-subagent-list-identity-projection.md) and [projected token usage](2026-07-29-projected-token-usage-and-request-context.md) decisions remain current; their domain folds move to the state table without changing their user-facing values.
 

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-19-session-projection-state-and-client-views.zh.md

@@ -12,11 +12,11 @@
 
 `SessionProjectionStateMap` 是 host 折叠状态的 merge-extensible 类型表。每个 `ProjectionDefinition` key 都属于此表并提供 `stateSchema`;缓存行只有通过校验后才能为折叠提供初始状态。`SessionProjectionMap` 保留原有名称和语义,继续作为唯一的客户端可见全量值类型表,因此 `title: string | null` 等既有客户端数据结构保持不变。
 
-如果一个单元的 key 也存在于 `SessionProjectionMap`,该单元就提供 `wire.viewSchema` 与 `wire.view`。每个单元的状态都会写入检查点——client-visible 与 host-only 一视同仁;`persist` 选择项已移除,任何单元都不能悄悄跳过持久化缓存。快照 API 只返回 `SessionProjectionMap`,因此内部状态不会进入 API 载荷。host 代码通过 `stateOf(session, key)` 读取一份当前状态;返回的是借用引用,不得修改。
+如果一个单元的 key 也存在于 `SessionProjectionMap`,该单元就提供 `wire.viewSchema` 与 `wire.view`。每个单元的状态都会写入检查点——client-visible 与 host-only 一视同仁;`persist` 选择项已移除,任何单元都不能悄悄跳过持久化缓存。快照 API 只枚举带 `wire` 的定义,因此漏传受众标记也无法让内部状态进入 API 载荷。host 代码通过 `stateOf(session, key)` 读取一份当前状态;返回的是借用引用,不得修改。
 
 ## 结果
 
-投影状态和客户端值分别获得类型与校验,同时不引入第二套客户端 DTO 词汇。单元可以保留更丰富的 host 状态,并暴露紧凑或兼容既有结构的客户端值。畸形缓存状态不能为 `viewCheckpoint` 提供数据;恢复会拒绝畸形状态,并由缓存既有的全量读取回退从日志重建。host 消费方可以用同一套增量折叠替换私有日志扫描。
+投影状态和客户端值分别获得类型与校验,同时不引入第二套客户端 DTO 词汇。单元可以保留更丰富的 host 状态,并暴露紧凑或兼容既有结构的客户端值。畸形缓存状态不能为 `viewCheckpoint` 提供数据;恢复会拒绝畸形状态,并由缓存既有的全量读取回退从日志重建。host 消费方可以用同一套增量折叠替换私有日志扫描,而载体快照无法枚举 host-only 状态。
 
 原始 [session-projection 提案](../../proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md)已记录这次拆分。既有的 [subagent 身份投影](2026-08-06-subagent-list-identity-projection.zh.md)与[投影化 token 用量](2026-07-29-projected-token-usage-and-request-context.zh.md)决策仍然有效;其中的领域折叠迁入状态表,不改变面向用户的值。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml

@@ -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-06-sandbox.md
-2026-07-06-sandbox.md: 9a3f61cf853ef2866180611ae12d8dbd1ad71989
-2026-07-06-sandbox.zh.md: 2b6d17322bbd22d65d986c0a80dc23d8ebf6cee3
+2026-07-06-sandbox.md: c4153c6ef47b6d11290094adf06b8107418be889
+2026-07-06-sandbox.zh.md: 9191b1b8d2d4e65acab8b935d0da170066876aea

+ 7 - 6
.agents/notes/implemented/feature/2026-07-06-sandbox.md

@@ -89,10 +89,10 @@ Left open: what a durable grant's scope identity is beyond the sandbox mode —
 #### Per-session modes: the session log as the store
 
 ```
-effective(session) = findLast(the session's knob events)?.value ?? the composition-config default
+effective(session) = sessionProjections.stateOf(session, '<knob>') ?? the composition-config default
 ```
 
-The default is composition config (`cordis.yml`) — operator-owned, process-wide. A runtime switch is a session-scoped override recorded as one log-only event in that session's log. Restart immunity and multi-session isolation follow from replay, with no external config store. The in-process subagent driver snapshots a parent's explicit override at delegation and seeds a source-tagged event after the child's optional fork prefix, so delegation cannot fall back to a wider default ([decision](2026-07-25-subagent-policy-inheritance.md)).
+The default is composition config (`cordis.yml`) — operator-owned, process-wide. A runtime switch is a session-scoped override recorded as one log-only event in that session's log; the knob's projection unit folds it, and every unit's state is checkpointed, so restart immunity and multi-session isolation follow from the durable projection cache plus log replay, with no external config store. The in-process subagent driver snapshots a parent's explicit override at delegation and seeds a source-tagged event after the child's optional fork prefix, so delegation cannot fall back to a wider default ([decision](2026-07-25-subagent-policy-inheritance.md)).
 
 **One event per knob, owned by its domain** — the merge-extensible `SessionEventMap` idiom every existing event family already follows (`approval/*` in `dsh-user-approval`, `hook/*` in the hooks packages):
 
@@ -103,7 +103,7 @@ interface SessionEventMap {
 }
 ```
 
-Each owner exports the same three-piece kit: the event declaration, a pure fold (`effectiveSandboxMode(events)` / `effectiveApprovalPolicy(events)` — a `findLast`, typed to the domain's closed union), and THE write path (`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)` — a switch IS its event; nothing mutates state out of band). No shared owner service, no generic facts map, no registry: a third knob copies the ~40-line pattern into its own package. Execution follows the fold on both sides — the bash tool's per-call stamp reads it as the middle rung of the § Escalation precedence chain, and the approval seam's `'never'` gate is [the approval Agent Note](2026-07-06-approval-seam.md)'s side of the same pattern.
+Each owner contributes the same pieces: the event declaration, one projection unit registered on the required `ctx.sessionProjections` registry (`key` `sandboxMode` / `approvalPolicy`, `stateVersion` 1, a zod `stateSchema` that validates persisted state, `init` to `null`, and `apply` — the fold, a find-last-shaped transition over its knob event typed to the domain's closed union), and THE write path (`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)` — a switch IS its event; nothing mutates state out of band). The registry drives every unit eagerly over committed session events and checkpoints all states uniformly — host-only units included, no `persist` opt-in — so restart immunity and multi-session isolation rest on the durable cache plus log replay rather than a per-package read helper. It is shared framework infrastructure, not a facts map with an owner service: each domain keeps its own event and unit, and a third knob writes its own event, unit, and setter in its own package and registers the unit — the drive, checkpointing, and read face come from the registry, and there is no fold export to copy. Host readers read the folded override through `stateOf(session, key)` (`SandboxPolicyService.overrideOf` and `ApprovalService.overrideOf` are those reads), and contributors and readers both require `sessionProjections` ([the mandatory projection seam](../architecture/2026-08-19-session-projection-mandatory-seam.md)). Execution follows the projected state on both sides — the bash tool's per-call stamp reads it as the middle rung of the § Escalation precedence chain, and the approval seam's `'never'` gate is [the approval Agent Note](2026-07-06-approval-seam.md)'s side of the same pattern.
 
 Before each proposed step, sandbox and approval policy are rendered as ordered contributions to one desired policy-context message. The listener reconciles that message against session history, the claimed batch, and its pending `next-step` inbox entry. Once claimed and entered, the loop records the complete sourced `user/message`; both `'ask'` and `'never'` are explicit, so neither owner needs switch narration or last-told state.
 
@@ -164,7 +164,7 @@ What shipped pins — the tiers in Testing hold each:
 - A resumed session's overrides enter its first new policy-context message with no catch-up state; a composition default changed while the process was down likewise appears in that message.
 - Two concurrent sessions never see each other's state or notices.
 - Two concurrent project sessions in one Cordis context resolve independent workspace roots; bash and fs writes succeed inside the calling session's cwd and fail against its neighbor's cwd.
-- Policy ownership stays in plugins through `SessionEventMap` merging, inbox mutation from `agent/pre-step`, and capability-owned resolution; the generic loop only claims and records the final entered batch.
+- Policy ownership stays in plugins through `SessionEventMap` merging, the projection units registered on the required `ctx.sessionProjections` registry, inbox mutation from `agent/pre-step`, and capability-owned resolution; the generic loop only claims and records the final entered batch.
 
 Costs and accepted limits:
 
@@ -191,7 +191,7 @@ Costs and accepted limits:
 - **Which tools actually run confined?** OS subprocesses through `ctx.shell` — the bash tools, and hook commands transitively — plus the filesystem tools (`read`/`write`/`edit`) through the sandboxed `ctx.fs` provider (the [cross-family fs sandbox RFC](2026-07-14-cross-family-fs-sandbox.md)): bash confines via the OS runner, fs via an in-process path fence, both keying off the same `ctx.sandboxPolicy` mode. web/todo stay in-process and unfenced (web's only effect is network, outside the file-effect mode vocabulary).
 - **Does a granted escalation persist?** No. The grant is consumed by the exact foreground or background call that asked; every neighboring call keeps its own effective mode. A later background denial surfaces through `job_output` and may ground a new exact-command retry.
 - **When does a runtime mode switch take effect?** Once its session event commits, the next pre-step policy-context reconciliation and the next capability resolution fold the new mode. The sourced context message records what the model was told, and any later denial names the same policy at the point of use.
-- **What survives a restart — and what if the operator changed the config default while the process was down?** Overrides replay from the session log (`effective = fold ?? config`), so a resumed session keeps its modes with zero catch-up machinery; a default that drifted offline enters the next full policy-context message.
+- **What survives a restart — and what if the operator changed the config default while the process was down?** Overrides restore from the durable projection cache and refold over the session log (`effective = projected override ?? config`), so a resumed session keeps its modes with zero catch-up machinery; a default that drifted offline enters the next full policy-context message.
 - **What does `enforcement: 'partial'` on a result mean?** The selected backend enforces the subset its kernel ABI governs — e.g. Landlock before ABI v3 does not govern path truncate — and says so structurally instead of refusing the host; the probe's report line distinguishes the cases. The bwrap and Seatbelt profiles govern every promised file effect by construction, so they always report `full`.
 
 ## Prior art
@@ -201,5 +201,6 @@ In-repo precedents this design copies or contrasts with:
 - [The capability-seams Agent Note](../architecture/2026-06-13-capability-seams.md) — the Service Definition / Service Provider / Consumer split and the "don't split preemptively" timing rule the second consumer satisfied.
 - The `dsh-shell` request/spec split ([the bash vocabulary catalog](../../../../docs/subsystems/shell.md)) — the complete `sandboxPolicy` rides its per-call carrier, and the explicit-`resolve()` defaulting convention.
 - [The approval seam Agent Note](2026-07-06-approval-seam.md) — the channel escalation asks through; its answerer waterfall, audit pair, and one-package rationale are recorded there.
-- [Event-sourced sessions](../architecture/2026-06-11-event-sourced-sessions.md) and [standalone log-only events](../simplification/2026-07-28-remove-synthetic-log-only-turns.md) — the log-as-store foundation the per-session modes fold over, and the explicit durability boundary the anchoring design obeys.
+- [Session projections as a required seam](../architecture/2026-08-19-session-projection-mandatory-seam.md) — the required `ctx.sessionProjections` registry the knob units register on; host readers read the folded override through `stateOf(session, key)`.
+- [Event-sourced sessions](../architecture/2026-06-11-event-sourced-sessions.md) and [standalone log-only events](../simplification/2026-07-28-remove-synthetic-log-only-turns.md) — the log-as-store foundation the projection units fold over, and the explicit durability boundary the anchoring design obeys.
 - [The interception extension-points Agent Note](2026-06-30-interception-extension-points.md) — the `tools/pre-execute` vocabulary the escalation gate deliberately does not reuse (an escalating call has no pre-execute moment of its own).

+ 10 - 9
.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md

@@ -18,7 +18,7 @@ harness 是一个 SDK,因此约束必须是开发者可组合的能力:是
 
 ### 部署方式
 
-四条 `cordis.yml` 条目即可将一个无约束的编码 agent 转变为沙箱产品路径;[base profile bundle](../../../../packages/bundle/base/cordis.patch.yml)默认使用此组合:
+四条 `cordis.yml` 条目即可将一个无约束的编码 agent 转变为沙箱产品路径;[基础 profile 组合包](../../../../packages/bundle/base/cordis.patch.yml)默认使用此组合:
 
 ```yaml
 - id: sandbox
@@ -40,7 +40,7 @@ harness 是一个 SDK,因此约束必须是开发者可组合的能力:是
 
 配置错误会显式导致失败:`mode` 不在封闭词汇中时在插件加载时被拒绝;主机上没有可用后端时在 `confine()` 阶段抛出结构化的 `SANDBOX_UNAVAILABLE`,而非降级为无约束执行。如果所选 runner 以可归因的 `ENOENT` 或 `EACCES` 拒绝,消费方会在任何命令开始前通过 spawn 通道报告同一基础设施错误;其他 spawn 错误仍保留本地命令启动语义,同时也不会运行任何内容。`dsh-sandbox-local` 上的 `runnerCommand` 是运维人员对一个 bwrap 兼容 runner 的显式断言(跳过链和探测);它同时充当 keyless 测试的确定性 fake-runner 钩子。
 
-被拒绝的文件操作返回 `[sandbox: file access denied under <mode> mode]` 标记,并附带不要绕过拒绝的指令。约束执行器添加配对的 `sandbox_permissions` 和 `justification` 字段,用于一次经批准的重试,该重试必须严格宽于会话的有效模式。授权仅放宽该次重试;拒绝则不执行任何内容,返回 `the user rejected escalating this command to "<mode>"`,且不允许再次请求。由归属方派生的待处理策略上下文会说明当前文件策略,但不会取代这些强制执行边界。当 `dsh-permission-presets` 与某个 UI 适配器一起组合时,一个 preset 同时选定两个旋钮值;不匹配的组合折叠为 `custom`。[ACP 应用 bundle](../../../../packages/bundle/acp-app/README.zh.md)不挂载该 UI 服务,而是显式选定其部署模式。
+被拒绝的文件操作返回 `[sandbox: file access denied under <mode> mode]` 标记,并附带不要绕过拒绝的指令。约束执行器添加配对的 `sandbox_permissions` 和 `justification` 字段,用于一次经批准的重试,该重试必须严格宽于会话的有效模式。授权仅放宽该次重试;拒绝则不执行任何内容,返回 `the user rejected escalating this command to "<mode>"`,且不允许再次请求。由归属方派生的待处理策略上下文会说明当前文件策略,但不会取代这些强制执行边界。当 `dsh-permission-presets` 与某个 UI 适配器一起组合时,一个 preset 同时选定两个旋钮值;不匹配的组合折叠为 `custom`。[ACP(Agent Client Protocol)应用组合包](../../../../packages/bundle/acp-app/README.zh.md)不挂载该 UI 服务,而是显式选定其部署模式。
 
 ### 设计细节
 
@@ -89,10 +89,10 @@ Landlock launcher 源码和包家族位于 `native/landlock-run`,与 harness 
 #### 按会话模式:会话日志即存储
 
 ```
-effective(session) = findLast(the session's knob events)?.value ?? the composition-config default
+effective(session) = sessionProjections.stateOf(session, '<knob>') ?? the composition-config default
 ```
 
-默认值是组合配置(`cordis.yml`)——由运维人员拥有、作用于整个进程。运行时切换是会话范围的覆盖,以一条仅日志事件记录在该会话的日志中。重启后仍然有效以及多会话隔离均由回放自然保证,且不存在任何外部配置存储。进程内 subagent 驱动器在委派时对父级的显式覆盖项获取快照,并在子 agent 可选的 fork 前缀之后预置一条带来源标记的事件,因此委派无法回退到更宽的默认值([决策](2026-07-25-subagent-policy-inheritance.zh.md))。
+默认值是组合配置(`cordis.yml`)——由运维人员拥有、作用于整个进程。运行时切换是会话范围的覆盖,以一条仅日志事件记录在该会话的日志中;该旋钮的投影单元折叠这条事件,且所有单元的状态都会被统一检查点,因此重启后仍然有效与多会话隔离来自持久化投影缓存加日志回放,且不存在任何外部配置存储。进程内 subagent 驱动器在委派时对父级的显式覆盖项获取快照,并在子 agent 可选的 fork 前缀之后预置一条带来源标记的事件,因此委派无法回退到更宽的默认值([决策](2026-07-25-subagent-policy-inheritance.zh.md))。
 
 **每个旋钮一种事件,由其领域拥有**——这是每个既有事件族已遵循的可合并扩展 `SessionEventMap` 惯用法(`dsh-user-approval` 中的 `approval/*`、hooks 包中的 `hook/*`):
 
@@ -103,7 +103,7 @@ interface SessionEventMap {
 }
 ```
 
-每个拥有者导出相同的三件套:事件声明、纯 fold(`effectiveSandboxMode(events)` / `effectiveApprovalPolicy(events)`——一个 `findLast`,类型化到领域的封闭联合),以及唯一的写入路径(`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)`——切换即其事件;没有任何东西在带外修改状态)。无需共享的归属服务、通用 facts map 或注册表:第三个配置项只需将约 40 行模式复制到自己的包中。执行在两侧都遵循 fold——bash 工具的按调用盖章将其作为 § 升级机制优先级链的中间层读取,approval seam 的 `'never'` 门控是[批准 Agent Note](2026-07-06-approval-seam.zh.md) 同一模式的另一侧。
+每个拥有者贡献同一套构成:事件声明,一个注册在必需注入的 `ctx.sessionProjections` 注册表上的投影单元(`key` 为 `sandboxMode` / `approvalPolicy`,`stateVersion` 1,一个校验持久化状态的 zod `stateSchema`,`init` 为 `null`,以及 `apply`——即 fold,一个对其旋钮事件的 find-last 形态转移,类型化到领域的封闭联合),以及唯一的写入路径(`setSandboxMode(session, mode)` / `setApprovalPolicy(session, policy)`——切换即其事件;没有任何东西在带外修改状态)。注册表在每条已提交的会话事件上急切驱动所有单元,并统一检查点所有状态——host-only 单元也不例外,无 `persist` 开关——因此重启后仍然有效与多会话隔离依托持久化缓存加日志回放,而非每个包自带的读取辅助函数。它是共享的框架基础设施,而非带归属服务的 facts map:每个领域保留自己的事件与单元,第三个配置项只需在自己的包中写好事件、单元与 setter 并注册该单元——驱动、检查点与读取面都来自注册表,无需复制任何 fold 导出。host 读取方经 `stateOf(session, key)` 读取折叠后的覆盖(`SandboxPolicyService.overrideOf` 与 `ApprovalService.overrideOf` 就是这些读取),且贡献方与读取方都必需注入 `sessionProjections`([必需投影 seam](../architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。执行在两侧都遵循投影状态——bash 工具的按调用盖章将其作为 § 升级机制优先级链的中间层读取,approval seam 的 `'never'` 门控是[批准 Agent Note](2026-07-06-approval-seam.zh.md) 同一模式的另一侧。
 
 每次拟议步骤之前,沙箱策略与批准策略都会渲染为一条目标策略上下文消息中的有序贡献。监听器将该消息与会话历史、已领取批次及其待处理的 `next-step` inbox 条目协调。消息一旦被领取并进入步骤,循环就会记录完整且带来源的 `user/message`;`'ask'` 与 `'never'` 都会明确写入,因此两个归属方都无需切换叙述或「上次告知」状态。
 
@@ -164,7 +164,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自能力边
 - 恢复会话的覆盖项会进入其首条新策略上下文消息,无需追赶状态;进程停止期间变更的组合默认值也会出现在该消息中。
 - 两个并发会话永远看不到彼此的状态或通知。
 - 同一个 Cordis 上下文中的两个并发项目会话解析各自独立的工作区根目录;bash 和 fs 写入在调用方会话的 cwd 内成功,对其相邻会话的 cwd 则失败。
-- 策略归属仍通过 `SessionEventMap` 合并、从 `agent/pre-step` 变更 inbox,以及由能力归属方拥有的解析留在插件中;通用循环只领取并记录最终进入步骤的批次。
+- 策略归属仍通过 `SessionEventMap` 合并、注册在必需注入的 `ctx.sessionProjections` 注册表上的投影单元、从 `agent/pre-step` 变更 inbox,以及由能力归属方拥有的解析留在插件中;通用循环只领取并记录最终进入步骤的批次。
 
 代价与已接受的限制:
 
@@ -187,11 +187,11 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自能力边
 - **如何区分损坏的沙箱与失败的命令?** 提供方 argv 的任何 spawn 拒绝都能证明受限启动从未开始,但只有在调用方拥有的 workdir 可用,且 Node 为该 argv[0] 报告可归因的 `ENOENT` 或 `EACCES` 时,才能据此判定 runner 损坏。没有精确错误路径的裸 `syscall: 'spawn'` 和其他所有拒绝仍是普通的命令启动错误。进程启动后,只有当 `runnerFailureRules` 中某一条目同时匹配其可选退出码门控,以及排除整行精确信息性行后的一行致命 stderr 诊断时,runner 失败才会优先于拒绝。前台失败会抛出结构化的 `SANDBOX_UNAVAILABLE`,并附带 spawn 错误或匹配行作为详细信息;遭异步拒绝或已结算的后台任务则盖章 `sandbox.runnerFailed` 并渲染自己的标记。如果 `SubprocessRuntime` 同步抛出同样带有 runner 路径的 `ENOENT`/`EACCES` 形态,后台启动会抛出该结构化错误;其他同步错误原样传播。Landlock 部分强制执行通知加上普通子进程失败时,仍返回命令结果。
 - **在没有后端的平台上会发生什么?** `confine()` 抛出失败关闭的 `SANDBOX_UNAVAILABLE`,命令永不 spawn。
 - **`bwrap` 已安装在我的主机上但不可用(禁用了非特权 userns、LSM 拒绝 `mount`)——会发生什么?** 链探测是功能性的——它构建并强制一个真实 profile 而非检查 `--version`——因此存在但不可用的 `bwrap` 探测失败,选择落到已打包的 Landlock launcher,结论在提供方生命周期内缓存。
-- **沙箱限制网络或进程可见性吗?** `SandboxMode` 只声称文件影响,并且没有后端声称限制网络。进程可见性因后端而异:bwrap 会取消共享 PID 命名空间,并挂载与其匹配的 procfs,因为宿主 `/proc/<pid>` 的魔法链接会绕过文件约束;Landlock 与 Seatbelt 则保持进程可见性不变(见[相关决策](../bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md))。网络限制是否成为自己的旋钮留在 § seam 中开放。
+- **沙箱限制网络或进程可见性吗?** `SandboxMode` 仅声称文件操作,没有后端声称网络。进程可见性取决于后端:bwrap 会 unshare PID 并挂载匹配的 procfs,因为宿主 `/proc/<pid>` 魔法链接会绕过文件约束;Landlock 与 Seatbelt 则保持进程可见性不变([决策](../bug-fix/2026-08-06-bwrap-private-pid-namespace.zh.md))。网络限制是否成为自己的旋钮留在 § seam 中开放。
 - **哪些工具实际在约束下运行?** 通过 `ctx.shell` 的 OS 子进程——bash 工具及传递性的钩子命令——再加上通过沙箱化 `ctx.fs` 提供方运行的文件系统工具(`read`/`write`/`edit`,见[跨工具族 fs 沙箱 RFC](2026-07-14-cross-family-fs-sandbox.zh.md)):bash 通过 OS runner 约束,fs 通过进程内路径围栏约束,二者都以同一个 `ctx.sandboxPolicy` 模式为键。web/todo 仍在进程内且不受限制(web 的唯一效果是网络,不在文件效果模式词汇内)。
 - **授权的升级会持久化吗?** 不会。授权由发起请求的确切前台或后台调用消费;每个相邻调用保留自己的有效模式。后续的后台拒绝通过 `job_output` 呈现,并且可以作为一次新的精确命令重试的依据。
 - **运行时模式切换何时生效?** 一旦其会话事件提交,下一次 pre-step 策略上下文协调与下一次能力解析都会折叠新模式。带来源的上下文消息会记录模型收到的内容,之后的任何拒绝都会在使用点命名同一策略。
-- **重启后什么存活——如果运维人员在进程停止期间改了配置默认值呢?** 覆盖从会话日志回放(`effective = fold ?? config`),因此恢复的会话以零追赶机制保持其模式;离线漂移的默认值会进入下一条完整策略上下文消息。
+- **重启后什么存活——如果运维人员在进程停止期间改了配置默认值呢?** 覆盖从持久化投影缓存恢复并在会话日志上重新折叠(`effective = 投影覆盖 ?? config`),因此恢复的会话以零追赶机制保持其模式;离线漂移的默认值会进入下一条完整策略上下文消息。
 - **结果上的 `enforcement: 'partial'` 是什么意思?** 所选后端强制其内核 ABI 管控的子集——例如 ABI v3 之前的 Landlock 不管控路径 truncate——并以结构化方式如此声明而非拒绝主机;探测的报告行区分各种情况。bwrap 和 Seatbelt profile 构造上管控所有承诺的文件操作,因此始终报告 `full`。
 
 ## 先例
@@ -201,5 +201,6 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自能力边
 - [能力 seam Agent Note](../architecture/2026-06-13-capability-seams.zh.md)——Service Definition/Service Provider/Consumer 拆分与「不要过早拆分」的时机规则(第二个消费方满足了该规则)。
 - `dsh-shell` 的 request/spec 拆分([bash 词汇目录](../../../../docs/subsystems/shell.zh.md))——完整的 `sandboxPolicy` 搭载其按调用载体,以及显式 `resolve()` 默认约定。
 - [批准 seam Agent Note](2026-07-06-approval-seam.zh.md)——升级请求通过的通道;其应答器 waterfall(瀑布式事件)、审计对和单包理由记录在那里。
-- [事件溯源会话](../architecture/2026-06-11-event-sourced-sessions.zh.md)与[独立纯日志事件](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)——按会话模式 fold 所依赖的日志即存储基础,以及锚定设计遵守的显式持久性边界。
+- [会话投影作为必需 seam](../architecture/2026-08-19-session-projection-mandatory-seam.zh.md)——旋钮单元注册所在的必需 `ctx.sessionProjections` 注册表;host 读取方经 `stateOf(session, key)` 读取折叠后的覆盖。
+- [事件溯源会话](../architecture/2026-06-11-event-sourced-sessions.zh.md)与[独立纯日志事件](../simplification/2026-07-28-remove-synthetic-log-only-turns.zh.md)——投影单元折叠所依赖的日志即存储基础,以及锚定设计遵守的显式持久性边界。
 - [拦截扩展点 Agent Note](2026-06-30-interception-extension-points.zh.md)——`tools/pre-execute` 词汇,升级门控刻意不复用它(升级调用没有自己的 pre-execute 时刻)。

+ 2 - 2
.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.i18n.yaml

@@ -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-14-cross-family-fs-sandbox.md
-2026-07-14-cross-family-fs-sandbox.md: 9bd62b5ad7048f05d1cc613305662c0a36a409df
-2026-07-14-cross-family-fs-sandbox.zh.md: d1dba9da9ed7e367504143ae26d05e7e9a8eb696
+2026-07-14-cross-family-fs-sandbox.md: 08b95541770b3c150a694a3f0731cdc42fe3c3fd
+2026-07-14-cross-family-fs-sandbox.zh.md: 5ee4f4f5d6b9193f83a243b49bc9ae2627d63920

+ 4 - 4
.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md

@@ -21,8 +21,8 @@ Three coordinated pieces, all composed from the leaf `cordis.yml`, none touching
 `packages/sandbox/sandbox-policy/` (`@deepseek-ai/dsh-sandbox-policy`) registers `ctx.sandboxPolicy`, the single owner of the deployment's sandbox policy:
 
 - `Config`: `mode` (the closed `SandboxMode` union, default `read-only`) and `workspaceRoot` (default the process cwd, resolved absolute). Misconfiguration fails loud at load.
-- The per-session override event `sandbox/mode`, with its pure fold (`effectiveSandboxMode(events)`), its write path (`setSandboxMode(session, mode)`), and `SANDBOX_MODES`. The event is policy state — consumed by two families — so it lives here, not in either capability's seam. Its shape and log-only semantics match the `approval/*` precedent.
-- `resolve({ session?, mode? })`, which returns a complete per-call `SandboxExecutionPolicy`: explicit approved mode > the session fold > `defaultMode`, and the session's immutable cwd > configured `workspaceRoot` fallback.
+- The per-session override event `sandbox/mode`, folded by the `sandboxMode` projection unit this package registers on the required `ctx.sessionProjections` registry (`stateVersion` 1, host-only; `stateOf(session, 'sandboxMode')` is the host read), its write path (`setSandboxMode(session, mode)`), and `SANDBOX_MODES`. The event is policy state — consumed by two families — so it lives here, not in either capability's seam. Its shape and log-only semantics match the `approval/*` precedent, and contributors and readers both require `sessionProjections` ([the mandatory projection seam](../architecture/2026-08-19-session-projection-mandatory-seam.md)).
+- `resolve({ session?, mode? })`, which returns a complete per-call `SandboxExecutionPolicy`: explicit approved mode > the session's projected override > `defaultMode`, and the session's immutable cwd > configured `workspaceRoot` fallback.
 - `defaultMode` / `workspaceRoot` accessors retained as deployment fallbacks and the capability-advertisement fact.
 
 `dsh-bash-sandbox` carries no sandbox config of its own — it injects `sandboxPolicy` and uses its deployment fallback only for direct calls. `dsh-tool-bash` and `dsh-tool-fs` pass the active session to `ctx.sandboxPolicy.resolve()`, so both receive the same effective mode and cwd root on every call; `dsh-permission-presets` presets and the ACP bridge write through the relocated setter. The seams that own bash and fs execution remain session-free — the session dependency lives in the policy package and tool consumers.
@@ -78,7 +78,7 @@ What shipped — the tiers in § Testing hold each:
 - Under `read-only`, `write`/`edit` return the `[sandbox: file access denied under read-only mode]` marker and the disk is untouched; `read`/`listDir` behave identically to `dsh-fs-local`.
 - Under `workspace-write`, mutations land under the workspace root and the temp areas and are denied outside; the containment matrix — `..` traversal, absolute paths outside, a pre-existing symlinked directory inside pointing out, a new file created under such a symlink, and alias-equivalent root spellings — denies every escape while admitting the same directory identity on real disks.
 - A denied fs mutation retried once with `sandbox_permissions` + `justification` prompts through the composed approval chain; a grant runs exactly that call under the wider mode and the write lands; rejected/cancelled/unavailable each produce their verbatim fail-closed text and mutate nothing.
-- One `permission` preset switch governs both families: after a session switches modes, the next bash call and the next fs mutation both honor the new mode from the same `sandbox/mode` fold.
+- One `permission` preset switch governs both families: after a session switches modes, the next bash call and the next fs mutation both honor the new mode from the same `sandboxMode` projection.
 - Concurrent sessions with different cwd roots carry different policies through the same service instances; neither family caches one session's root for the next call.
 - A direct `ctx.fs.writeText` with no per-call stamp is confined at the deployment default.
 - The escalation fields on `write`/`edit` exist exactly when the mounted `ctx.fs` confines, absent under `dsh-fs-local`.
@@ -93,6 +93,6 @@ Costs and accepted limits:
 
 ## Testing
 
-- Unit: `dsh-sandbox` pins the escalation ladder, the marker builders, the argument-pairing validation, and `approveEscalation`'s ordered fail-closed sequence (non-widening, no-approval, no-agent, each outcome), plus `writableRoots`/`canonicalPath`. `dsh-sandbox-policy` pins deployment fallback, session mode/root resolution, explicit-mode precedence, the fold/setter, load-time mode rejection, and HMR safety. `dsh-fs-sandbox` pins the per-policy fence and containment matrix (inside, temp area, absolute-outside, `..`, symlinked-out directory, new file under one, path-equals-root, filesystem-root, root-ending-in-separator, and alias-equivalent spelling) on a real filesystem, plus per-call override and HMR safety. `dsh-tool-fs` pins advertisement gating, complete policy resolution, denial-marker mapping, and the full escalation matrix (grant, reject, no-service, no-agent, pairing, non-confining guard). `dsh-tool-bash`, `dsh-bash-sandbox`, and `dsh-permission-presets` consume the same policy kit.
+- Unit: `dsh-sandbox` pins the escalation ladder, the marker builders, the argument-pairing validation, and `approveEscalation`'s ordered fail-closed sequence (non-widening, no-approval, no-agent, each outcome), plus `writableRoots`/`canonicalPath`. `dsh-sandbox-policy` pins deployment fallback, session mode/root resolution, explicit-mode precedence, the `sandboxMode` projection unit's fold and the setter, load-time mode rejection, and HMR safety. `dsh-fs-sandbox` pins the per-policy fence and containment matrix (inside, temp area, absolute-outside, `..`, symlinked-out directory, new file under one, path-equals-root, filesystem-root, root-ending-in-separator, and alias-equivalent spelling) on a real filesystem, plus per-call override and HMR safety. `dsh-tool-fs` pins advertisement gating, complete policy resolution, denial-marker mapping, and the full escalation matrix (grant, reject, no-service, no-agent, pairing, non-confining guard). `dsh-tool-bash`, `dsh-bash-sandbox`, and `dsh-permission-presets` consume the same policy kit.
 - Keyless e2e: one real Cordis context creates two agents with different session cwd roots, runs the shipped bash and fs tools concurrently, and world-verifies that own-project writes land while both cross-project writes are denied.
 - Snapshot: the acp-agent example composes `dsh-sandbox-policy` + `dsh-fs-sandbox`; the pinned header carries the fs escalation fields and the `sandbox/mode` event name, re-recorded once.

+ 4 - 4
.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.zh.md

@@ -21,8 +21,8 @@ Status: implemented
 `packages/sandbox/sandbox-policy/`(`@deepseek-ai/dsh-sandbox-policy`)注册 `ctx.sandboxPolicy`,即部署沙箱策略的唯一所有者:
 
 - `Config`:`mode`(封闭的 `SandboxMode` 联合,默认 `read-only`)与 `workspaceRoot`(默认进程 cwd,解析为绝对路径)。配置错误会在加载时明确报错。
-- per-session 覆盖事件 `sandbox/mode`,连同它的纯折叠(`effectiveSandboxMode(events)`)、写入路径(`setSandboxMode(session, mode)`)与 `SANDBOX_MODES`。该事件是策略状态——被两个家族消费——所以它归于此处,而不归于任一能力的 seam。它的形状与仅日志(log-only)语义遵循 `approval/*` 的先例。
-- `resolve({ session?, mode? })` 返回完整的单次调用 `SandboxExecutionPolicy`:显式批准的模式 > 会话折叠结果 > `defaultMode`,而会话中不可变的 cwd > 配置的 `workspaceRoot` 回退值。
+- per-session 覆盖事件 `sandbox/mode`,由本包注册在必需注入的 `ctx.sessionProjections` 注册表上的 `sandboxMode` 投影单元折叠(`stateVersion` 1、host-only;`stateOf(session, 'sandboxMode')` 即 host 读取),连同它的写入路径(`setSandboxMode(session, mode)`)与 `SANDBOX_MODES`。该事件是策略状态——被两个家族消费——所以它归于此处,而不归于任一能力的 seam。它的形状与仅日志(log-only)语义遵循 `approval/*` 的先例,且贡献方与读取方都必需注入 `sessionProjections`([必需投影 seam](../architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。
+- `resolve({ session?, mode? })` 返回完整的单次调用 `SandboxExecutionPolicy`:显式批准的模式 > 会话的投影覆盖 > `defaultMode`,而会话中不可变的 cwd > 配置的 `workspaceRoot` 回退值。
 - 保留 `defaultMode` / `workspaceRoot` 访问器,作为部署回退值与能力宣告依据。
 
 `dsh-bash-sandbox` 自身不再携带任何沙箱配置——它注入 `sandboxPolicy`,仅在直接调用时使用其中的部署回退值。`dsh-tool-bash` 与 `dsh-tool-fs` 把当前会话传给 `ctx.sandboxPolicy.resolve()`,因此两者每次调用都会取得相同的生效模式与 cwd 根目录;`dsh-permission-presets` 预设与 ACP(Agent Client Protocol)bridge 经由迁移后的 setter 写入。拥有 bash 与 fs 执行的 seam 仍不依赖会话——会话依赖归策略包与工具消费方所有。
@@ -78,7 +78,7 @@ Status: implemented
 - 在 `read-only` 下,`write`/`edit` 返回 `[sandbox: file access denied under read-only mode]` 标记,磁盘不受触动;`read`/`listDir` 与 `dsh-fs-local` 行为一致。
 - 在 `workspace-write` 下,变更落在工作区根与临时目录下,其外被拒;包含矩阵——`..` 穿越、指向外部的绝对路径、一个既有的、指向外部的工作区内符号链接目录、在这样一个符号链接下新建的文件,以及根路径的等价别名形式——在真实磁盘上拒绝每一种逃逸,同时允许文件系统认定为同一目录的路径。
 - 一个被拒的 fs 变更,携带 `sandbox_permissions` + `justification` 重试一次,会经组合的审批链提示;一次授权让恰好那一次调用在更宽的模式下运行且写入落盘;rejected/cancelled/unavailable 各自产生其逐字的 fail-closed 文案且不做任何变更。
-- 一次 `permission` 预设切换同时管辖两个家族:会话切换模式后,下一次 bash 调用与下一次 fs 变更都从同一个 `sandbox/mode` 折叠遵循新模式。
+- 一次 `permission` 预设切换同时管辖两个家族:会话切换模式后,下一次 bash 调用与下一次 fs 变更都从同一个 `sandboxMode` 投影遵循新模式。
 - cwd 根目录不同的并发会话通过同一组服务实例携带不同策略;两个家族都不会缓存某个会话的根目录供下一次调用使用。
 - 一次无 per-call 盖章的直连 `ctx.fs.writeText` 会被围栏于部署默认值。
 - `write`/`edit` 上的升级字段恰好在被挂载的 `ctx.fs` 受限时存在,在 `dsh-fs-local` 下不存在。
@@ -93,6 +93,6 @@ Status: implemented
 
 ## 测试
 
-- 单元:`dsh-sandbox` 钉住升级阶梯、标记构造器、参数配对校验,以及 `approveEscalation` 的有序 fail-closed 序列(非加宽、无 approval、无 agent、各结果),外加 `writableRoots`/`canonicalPath`。`dsh-sandbox-policy` 钉住部署回退、会话模式/根目录解析、显式模式优先级、折叠/setter、加载期模式拒绝,以及 HMR(热模块替换)安全。`dsh-fs-sandbox` 在真实文件系统上钉住按策略执行的围栏与包含矩阵(内部、临时目录、绝对路径-外部、`..`、指向外部的符号链接目录、其下的新建文件、路径等于根、文件系统根、以分隔符结尾的根、等价别名形式),外加 per-call 覆盖与 HMR 安全。`dsh-tool-fs` 钉住宣告门控、完整策略解析、拒绝标记映射,以及完整的升级矩阵(授权、拒绝、无服务、无 agent、配对、非受限守卫)。`dsh-tool-bash`、`dsh-bash-sandbox` 与 `dsh-permission-presets` 使用同一套策略工具集。
+- 单元:`dsh-sandbox` 钉住升级阶梯、标记构造器、参数配对校验,以及 `approveEscalation` 的有序 fail-closed 序列(非加宽、无 approval、无 agent、各结果),外加 `writableRoots`/`canonicalPath`。`dsh-sandbox-policy` 钉住部署回退、会话模式/根目录解析、显式模式优先级、`sandboxMode` 投影单元的折叠与 setter、加载期模式拒绝,以及 HMR(热模块替换)安全。`dsh-fs-sandbox` 在真实文件系统上钉住按策略执行的围栏与包含矩阵(内部、临时目录、绝对路径-外部、`..`、指向外部的符号链接目录、其下的新建文件、路径等于根、文件系统根、以分隔符结尾的根、等价别名形式),外加 per-call 覆盖与 HMR 安全。`dsh-tool-fs` 钉住宣告门控、完整策略解析、拒绝标记映射,以及完整的升级矩阵(授权、拒绝、无服务、无 agent、配对、非受限守卫)。`dsh-tool-bash`、`dsh-bash-sandbox` 与 `dsh-permission-presets` 使用同一套策略工具集。
 - 无密钥 e2e:一个真实 Cordis 上下文创建两个 agent,其会话的 cwd 根目录各不相同;系统并发运行正式发布的 bash 与 fs 工具,再通过外部可观察结果验证各自在所属项目中的写入成功,而两次跨项目写入都被拒绝。
 - 快照:acp-agent 示例组合 `dsh-sandbox-policy` + `dsh-fs-sandbox`;被钉住的 header 携带 fs 升级字段与 `sandbox/mode` 事件名,一次性重录。

+ 2 - 2
.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.i18n.yaml

@@ -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-22-durable-subagent-catalog-and-list-agents.md
-2026-07-22-durable-subagent-catalog-and-list-agents.md: 68dda3f0d483104db1bc722fbc811eb792cc1361
-2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: ac664453eeb535be7950791080abc6ef5c8397bd
+2026-07-22-durable-subagent-catalog-and-list-agents.md: 813ad0a57bf5399cf641f8a7271671da57ce3966
+2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: ab5351647da32384645c036c0c5e3e2281ba645a

+ 2 - 2
.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md

@@ -35,7 +35,7 @@ Session lineage is broader than subagent identity: an ordinary `ctx.sessions.for
 
 The published logical record is also the activity source: `SessionRecord.live` means `running`, while `live: false, persisted: true` means `inactive`. Activity comes directly from the trace and causes no additional child-log load. `inactive` encodes neither successful completion nor resumability: it may describe settled one-shot history or a continuable child for which `send_message` can materialize another Activation. Conversely, `running` says only that the session is live: a live continuable Agent outside the continuation manager's matching Activation still appears as `running`, but `send_message` rejects it as an ownership conflict. A child is not visible before its session is published, and no process-local Activation entry is added as a second candidate or activity source. Listing is a snapshot that may race publication, disposal, or a later message; `send_message` remains the authoritative delivery-time operation.
 
-The subagent service keeps `sessionQuery` optional so start and follow-up remain available without it. Its public `listChildren(parentSessionId: SessionId)` method resolves the optional service and dynamically loads the optional session-query runtime only when called; ordinary subagent imports, start, and follow-up therefore do not evaluate that package. Listing belongs directly to `SubagentRuntime`: it interprets the query's lineage, events, and live state without resolving the Activation-based continuation manager or consulting Agent registrations, Activations, or providers, so a deployment with sessions, `subagents`, and `sessionQuery` can list even when `agents` is absent. The method throws `SubagentError` with stable code `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` before loading the runtime or doing query work when the query service is absent. `@deepseek-ai/dsh-tool-subagent-control` exports separately loadable tool plugins: the `send_message` adapter requires only `subagents`, while the `list_agents` adapter requires both `subagents` and `sessionQuery` at load. A deployment may therefore use `send_message` without installing or loading session query; the list-tool fiber remains inactive until the required service is available, while another direct service consumer receives the same explicit call-time contract. This dependency posture — the optional `sessionQuery`, its error code, and the list tool's load requirement — is part of the superseded read path: the current codes (`SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE`, `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`) and the narrowed load requirement live in the [superseding note](../architecture/2026-08-06-subagent-list-identity-projection.md).
+The subagent service keeps `sessionQuery` optional so start and follow-up remain available without it. Its public `listChildren(parentSessionId: SessionId)` method resolves the optional service and dynamically loads the optional session-query runtime only when called; ordinary subagent imports, start, and follow-up therefore do not evaluate that package. Listing belongs directly to `SubagentRuntime`: it interprets the query's lineage, events, and live state without resolving the Activation-based continuation manager or consulting Agent registrations, Activations, or providers, so a deployment with sessions, `subagents`, and `sessionQuery` can list even when `agents` is absent. The method throws `SubagentError` with stable code `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` before loading the runtime or doing query work when the query service is absent. `@deepseek-ai/dsh-tool-subagent-control` exports separately loadable tool plugins: the `send_message` adapter requires only `subagents`, while the `list_agents` adapter requires both `subagents` and `sessionQuery` at load. A deployment may therefore use `send_message` without installing or loading session query; the list-tool fiber remains inactive until the required service is available, while another direct service consumer receives the same explicit call-time contract. This dependency posture — the optional `sessionQuery`, its error code, and the list tool's load requirement — is part of the superseded read path: the current listing contract lives in the [superseding note](../architecture/2026-08-06-subagent-list-identity-projection.md), where `sessionProjections` is a required injection of `SubagentRuntime` and `listChildren` reads it directly, so a missing projection registry fails at activation rather than at listing time, and `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE` (absent session store) remains the only stable availability error code.
 
 `listChildren(parentSessionId, signal?)` forwards the caller's signal to `traceSession()` and the conditional exact `readEvent()` operation. `listEvents()` has no cancellation parameter, so the listing path checks the signal before and after that await and after each candidate settles. If any query operation rejects after the signal aborts, the service normalizes the result to `SubagentError` with stable code `CANCELLED`; a backend abort error or a diagnostic-mapped query error cannot escape or become a successful partial listing.
 
@@ -93,7 +93,7 @@ The first version has no child deletion operation. If later product behavior del
 ## Testing
 
 - `packages/subagent/subagent/tests/service.spec.ts` pins descriptor v2 parsing for both modes and proves an unlabeled raw start resolves a one-shot descriptor before provider dispatch. `packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts` proves the local driver appends that descriptor inside the initial turn, returns the published id when cancellation lands in the factory-to-run handoff, and keeps result and handle-disposal failures on separate channels. Delegation-tool tests pin propagation of their existing display description and preserve independent result and disposal diagnostics.
-- `packages/subagent/subagent/tests/list-children.spec.ts` pins the current read path against a real composition of the session store, JSONL persistence, spawn/fork providers, the subagent service, and the projection registry — no query service — keylessly: live-only listing without persistence; loud `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` and `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE` even with zero children; the three-rung ladder (a live child never inspected, a cold child inspected exactly once, and the cache-hit, absent-key, absent-service, and poisoned-row second-rung cases); last-wins over multiple descriptors; malformed payloads and unknown versions diagnosed as `corrupt`; a failed cold inspection as one `unavailable` diagnostic retried on the next listing; a fork seed's ancestor descriptor listed under that identity; foreign-unit fold failures contained per child as `corrupt` on both the live and cold paths; `createdAt`-then-id ordering without ordinary forks; provider absence without child omission; compacted/uncompacted twins listing identically; a persisted-listing failure failing the whole enumeration; cancellation normalized to stable `CANCELLED`; typed stable error codes; and descendant listing's iterative stable pre-order, traversal through ordinary and one-shot intermediates, positioned diagnostics, lifecycle revalidation, and cancellation. A companion spec (retired together with the query-backed read path) rejected eager evaluation of the optional session-query runtime while importing the ordinary subagent surface.
+- `packages/subagent/subagent/tests/list-children.spec.ts` pins the current read path against a real composition of the session store, JSONL persistence, spawn/fork providers, the subagent service, and the projection registry — no query service — keylessly: live-only listing without persistence; non-activation without the projection registry (its mandatory injection seam) and a loud `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE` even with zero children; the three-rung ladder (a live child never inspected, a cold child inspected exactly once, and the cache-hit, absent-key, absent-service, and poisoned-row second-rung cases); last-wins over multiple descriptors; malformed payloads and unknown versions diagnosed as `corrupt`; a failed cold inspection as one `unavailable` diagnostic retried on the next listing; a fork seed's ancestor descriptor listed under that identity; foreign-unit fold failures contained per child as `corrupt` on both the live and cold paths; `createdAt`-then-id ordering without ordinary forks; provider absence without child omission; compacted/uncompacted twins listing identically; a persisted-listing failure failing the whole enumeration; cancellation normalized to stable `CANCELLED`; typed stable error codes; and descendant listing's iterative stable pre-order, traversal through ordinary and one-shot intermediates, positioned diagnostics, lifecycle revalidation, and cancellation. A companion spec (retired together with the query-backed read path) rejected eager evaluation of the optional session-query runtime while importing the ordinary subagent surface.
 - `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` pins the `list_agents` schema (one optional `scope` enum), the continuable-only projection that omits a healthy one-shot sibling while preserving diagnostics, registry-derived child/diagnostic/empty text forms, an end-to-end settled-child listing with its durable label, the descendants scope's pre-order parent/depth annotations across a live waiting branch, cancellation forwarding to both scopes, the no-agent rejection, the `agents` load requirement without `sessionQuery`, and HMR disposal.
 - The keyless SDK snapshot scenario [`subagent-list-agents`](../../../../snapshots/sdk/subagent-list-agents/) fences its second parent turn on a snapshot-only `subagent/end` marker, then executes `list_agents` for real against the subagent service, the projection registry, and JSONL persistence, rendering `<id> [ready] — <label>`.
 - The owner-local expected-output test `apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts` pins the current listing's model-visible diagnostic classification, including a descriptor-less settled child surfacing as a `corrupt` diagnostic.

+ 3 - 3
.agents/notes/implemented/feature/2026-07-22-durable-subagent-catalog-and-list-agents.zh.md

@@ -35,7 +35,7 @@ parent 到 child 的枚举是一项带消费方专用投影的服务功能。`Su
 
 已发布的逻辑记录同时也是活动状态来源:`SessionRecord.live` 表示 `running`,而 `live: false, persisted: true` 表示 `inactive`。活动状态直接来自追踪结果,不会导致额外加载 child 日志。`inactive` 既不表示执行成功,也不表示可恢复:它可能表示已结算的一次性历史,也可能表示 `send_message` 可以为其物化另一次 Activation 的可继续 child。反过来,`running` 只表示会话存活:位于继续执行管理器对应 Activation 之外的存活可继续 Agent 仍会显示为 `running`,但 `send_message` 会将其作为所有权冲突拒绝。child 会话发布前不可见,也不会添加进程内 Activation 条目作为第二个候选来源或活动状态来源。列表查询是一份快照,可能与发布、dispose 或后续消息发生竞态;`send_message` 仍是消息送达时的权威操作。
 
-subagent 服务将 `sessionQuery` 保持为可选依赖,因此没有该服务时仍可执行 start 和 follow-up。其公开的 `listChildren(parentSessionId: SessionId)` 方法只在被调用时才会解析这个可选服务,并动态加载可选的会话查询运行时;因此,普通 subagent 导入、start 和 follow-up 都不会触发该包求值。列表查询直接由 `SubagentRuntime` 负责:它解释查询返回的谱系、事件和存活状态,无需解析基于 Activation 的继续执行管理器,也不会查询 Agent 注册信息、Activation 或提供方;因此,仅包含会话、`subagents` 和 `sessionQuery` 的部署即使缺少 `agents` 也能执行列表查询。如果查询服务缺失,该方法会在加载运行时或执行查询工作前抛出 `SubagentError`,并携带稳定错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`。`@deepseek-ai/dsh-tool-subagent-control` 导出可分别加载的工具插件:`send_message` 适配器只要求 `subagents`,而 `list_agents` 适配器在加载时同时要求 `subagents` 和 `sessionQuery`。因此,部署可以在既不安装也不加载会话查询的情况下使用 `send_message`;列表工具 fiber 会在必需服务可用前保持未激活状态,而其他直接服务消费方会收到同一项明确的调用时约定。这一段的依赖姿态——可选 `sessionQuery`、其错误码与列表工具的加载要求——同属被取代的读路径:现行错误码(`SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE`、`SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`)与收窄后的加载要求以[取代记录](../architecture/2026-08-06-subagent-list-identity-projection.zh.md)为准。
+subagent 服务将 `sessionQuery` 保持为可选依赖,因此没有该服务时仍可执行 start 和 follow-up。其公开的 `listChildren(parentSessionId: SessionId)` 方法只在被调用时才会解析这个可选服务,并动态加载可选的会话查询运行时;因此,普通 subagent 导入、start 和 follow-up 都不会触发该包求值。列表查询直接由 `SubagentRuntime` 负责:它解释查询返回的谱系、事件和存活状态,无需解析基于 Activation 的继续执行管理器,也不会查询 Agent 注册信息、Activation 或提供方;因此,仅包含会话、`subagents` 和 `sessionQuery` 的部署即使缺少 `agents` 也能执行列表查询。如果查询服务缺失,该方法会在加载运行时或执行查询工作前抛出 `SubagentError`,并携带稳定错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`。`@deepseek-ai/dsh-tool-subagent-control` 导出可分别加载的工具插件:`send_message` 适配器只要求 `subagents`,而 `list_agents` 适配器在加载时同时要求 `subagents` 和 `sessionQuery`。因此,部署可以在既不安装也不加载会话查询的情况下使用 `send_message`;列表工具 fiber 会在必需服务可用前保持未激活状态,而其他直接服务消费方会收到同一项明确的调用时约定。这一段的依赖姿态——可选 `sessionQuery`、其错误码与列表工具的加载要求——同属被取代的读路径:现行列表约定以[取代记录](../architecture/2026-08-06-subagent-list-identity-projection.zh.md)为准;在那里 `sessionProjections` 是 `SubagentRuntime` 的必需注入,`listChildren` 直接读取它,投影注册表缺失会在激活期失败而非列表调用时失败,`SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`(会话存储缺席)仍是唯一的稳定可用性错误码。
 
 `listChildren(parentSessionId, signal?)` 会把调用方的取消信号转发给 `traceSession()` 和条件性精确 `readEvent()` 操作。`listEvents()` 不接受取消参数,因此列表查询路径会在等待该操作的前后,以及每个候选处理完成后检查信号。如果取消信号触发后有查询操作以拒绝结算,服务会将结果归一化为 `SubagentError`,并携带稳定错误码 `CANCELLED`;后端中止错误或可映射为 diagnostic 的查询错误均不会逃逸,也不会使调用以成功的部分列表返回。
 
@@ -93,9 +93,9 @@ diagnostic 是瞬时查询结果,不属于会话事件或目录状态。推导
 ## 测试
 
 - `packages/subagent/subagent/tests/service.spec.ts` 固定两种模式下的描述符 v2 解析,并证明无标签的底层启动会在分发给提供方之前解析出一次性描述符。`packages/subagent/subagent-in-process-driver/tests/subagent-in-process-driver.spec.ts` 证明本地驱动会在初始轮次内追加该描述符,在取消落入工厂到 run 的交接窗口时返回已发布 id,并让结果与句柄释放失败保留在独立通道中。委派工具测试固定其现有显示说明的传递,并保留相互独立的结果与 dispose diagnostic。
-- `packages/subagent/subagent/tests/list-children.spec.ts` 针对由会话存储、JSONL 持久化、spawn/fork 提供方、subagent 服务与投影注册表构成的真实组合——不含查询服务——以无密钥方式钉住现行读取路径:无持久化时的仅存活列表;零 children 也响亮报 `SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE` 与 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`;三级阶梯(存活 child 从不检查、冷 child 恰好检查一次,以及缓存命中、key 缺席、服务缺席、行中毒四个第二级用例);多描述符 last-wins 取末者;载荷格式错误与未知版本诊断为 `corrupt`;冷检查失败成一条 `unavailable` diagnostic 并在下次列表重试;fork seed 中的祖先描述符按该身份列出;外部 unit 折叠失败在存活与冷两条路径上按 child 收纳为 `corrupt`;按 `createdAt` 再按 id 排序且不列普通 fork;提供方缺失时不排除 child;压缩与未压缩的孪生 child 列表结果一致;持久化列表失败使整次枚举失败;取消稳定归一化为 `CANCELLED`;带类型的稳定错误码;以及后代列表的迭代式稳定 pre-order、穿过普通与一次性中间节点、带位置 diagnostic、生命周期复验与取消。一个伴随规格(已随查询式读取路径一起退役)曾在导入普通 subagent surface 时拒绝对可选 session-query 运行时的 eager 求值。
+- `packages/subagent/subagent/tests/list-children.spec.ts` 针对由会话存储、JSONL 持久化、spawn/fork 提供方、subagent 服务与投影注册表构成的真实组合——不含查询服务——以无密钥方式钉住现行读取路径:无持久化时的仅存活列表;投影注册表缺席时服务无法激活(其必需注入 seam),会话存储缺席时即使零 children 也响亮报 `SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE`;三级阶梯(存活 child 从不检查、冷 child 恰好检查一次,以及缓存命中、key 缺席、服务缺席、行中毒四个第二级用例);多描述符 last-wins 取末者;载荷格式错误与未知版本诊断为 `corrupt`;冷检查失败成一条 `unavailable` diagnostic 并在下次列表重试;fork seed 中的祖先描述符按该身份列出;外部 unit 折叠失败在存活与冷两条路径上按 child 收纳为 `corrupt`;按 `createdAt` 再按 id 排序且不列普通 fork;提供方缺失时不排除 child;压缩与未压缩的孪生 child 列表结果一致;持久化列表失败使整次枚举失败;取消稳定归一化为 `CANCELLED`;带类型的稳定错误码;以及后代列表的迭代式稳定 pre-order、穿过普通与一次性中间节点、带位置 diagnostic、生命周期复验与取消。一个伴随规格(已随查询式读取路径一起退役)曾在导入普通 subagent surface 时拒绝对可选 session-query 运行时的 eager 求值。
 - `packages/subagent/tool-subagent-control/tests/list-agents.spec.ts` 固定 `list_agents` 的 schema(一个可选 `scope` 枚举)、只保留可继续 child 且排除健康的一次性 sibling、同时保留 diagnostic 的投影、由注册表推导的 child/diagnostic/空结果文本形式、带持久化 label 的已结束 child 端到端列表、descendants scope 在在线 waiting 分支上的 pre-order parent/depth 注释、两个 scope 的取消信号转发、无调用 agent 时的拒绝、要求 `agents` 但不再注入 `sessionQuery` 的加载约定,以及 HMR dispose。
-- 无密钥 SDK 快照场景 [`subagent-list-agents`](../../../../snapshots/sdk/subagent-list-agents/)使用仅限快照的 `subagent/end` 标记为第二个 parent 轮次设置边界,随后针对 subagent 服务、投影注册表和 JSONL 持久化真实执行 `list_agents`,渲染 `<id> [ready] — <label>`。
+- 无密钥 SDK 快照场景 [`subagent-list-agents`](../../../../snapshots/sdk/subagent-list-agents/) 使用仅限快照的 `subagent/end` 标记为第二个 parent 轮次设置边界,随后针对 subagent 服务、投影注册表和 JSONL 持久化真实执行 `list_agents`,渲染 `<id> [ready] — <label>`。
 - 归属方本地的预期输出测试 `apps/cli/tests/profiles/headless/tests/subagent-diagnostic.expected.e2e.ts` 钉住现行列表的模型可见诊断分类,包括无描述符的定局 child 以 `corrupt` diagnostic 出现。
 - 无密钥 ACP 快照场景 `subagent-published-run-failure` 会发布一个真实的一次性 child,注入相互独立的 run result 与 handle dispose 失败,并在 parent 工具结果中保留两项 diagnostic。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.i18n.yaml

@@ -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-31-permission-default-for-new-sessions.md
-2026-07-31-permission-default-for-new-sessions.md: b5bb72ee179760e4b58ae5ca1124f9948ed24d5d
-2026-07-31-permission-default-for-new-sessions.zh.md: 9383a1b184f610d2995fb3d6982d3f4001729822
+2026-07-31-permission-default-for-new-sessions.md: 5f62effa8d724b9ffac0e8db0b329c0c947da966
+2026-07-31-permission-default-for-new-sessions.zh.md: f8810d9ff55cd6f15c0a587c2ab331b3fb41fb5e

+ 1 - 1
.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.md

@@ -12,7 +12,7 @@ The Web General-settings page displayed Permission as a disabled skeleton even t
 
 `dsh-permission-presets` owns a `permission` Settings namespace with one `defaultPreset` field. Its base value is `Config.defaultPreset`, or the preset matching the composed sandbox and approval defaults when the config omits it. The schema derives its enum from the configured preset table, so Settings validates stored values and the Web client discovers the deployment's actual choices without duplicating them.
 
-The service reads the current Settings value synchronously at `session/created`. A genuinely fresh session receives three explicit events: `permission/preset`, `sandbox/mode`, and `approval/policy`. Those facts pin the permission selected at creation, so a later Settings change affects only later sessions. A seeded or partially initialized session preserves its effective knobs and receives only missing facts; it never adopts the latest user default while resuming. `Session` marks even an explicitly empty constructor seed with `session/end-seed`, so an empty persisted log cannot be mistaken for a fresh session.
+The service reads the current Settings value synchronously at `session/created`. A genuinely fresh session receives three explicit events: `permission/preset`, `sandbox/mode`, and `approval/policy`. Those facts pin the permission selected at creation, so a later Settings change affects only later sessions. A seeded or partially initialized session preserves its effective knobs and receives only missing facts; it never adopts the latest user default while resuming. `Session` marks even an explicitly empty constructor seed with `session/end-seed`, so an empty persisted log cannot be mistaken for a fresh session. The `permissions` projection folds that boundary with the three permission facts, letting session pinning read one incremental state instead of rescanning the log.
 
 The existing `/permission` command and `permissions` projection remain the current-session path. The browser plugin now contributes the Permission row to `settings.general.item`, reads the dynamic enum from the redacted Settings descriptor, and writes only `defaultPreset` through a revision-checked `settings.mutate`. The row injects its observable through the slot `hooks` compartment instead of binding a renderer-specific hook, and the Permission service sweeps already-live sessions when it mounts so HMR cannot leave an unpinned session. The ownerless General-settings package contributes no placeholder rows.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-31-permission-default-for-new-sessions.zh.md

@@ -12,7 +12,7 @@ Web「通用」设置页将「权限」显示为禁用的骨架控件,尽管 `
 
 `dsh-permission-presets` 拥有一个 `permission` Settings namespace,其中只有 `defaultPreset` 字段。它的基础值是 `Config.defaultPreset`;省略该配置时,则使用与组合后的沙箱和审批默认值匹配的 preset。schema 的 enum 从已配置的 preset 表派生,因此 Settings 既能校验已存储的值,Web 客户端也能发现部署中的实际选项,而无需重复定义。
 
-服务会在 `session/created` 时同步读取当前 Settings 值。真正的新会话会收到三个显式事件:`permission/preset`、`sandbox/mode` 和 `approval/policy`。这些事实将创建时选中的权限固定下来,因此后续 Settings 变更只影响之后的会话。带 seed 或只完成部分初始化的会话会保留其有效调节项,只补齐缺失的事实;恢复时绝不会采用最新的用户默认值。`Session` 甚至会用 `session/end-seed` 标记显式为空的构造器 seed,因此不能把空的持久化日志误认为新会话。
+服务会在 `session/created` 时同步读取当前 Settings 值。真正的新会话会收到三个显式事件:`permission/preset`、`sandbox/mode` 和 `approval/policy`。这些事实将创建时选中的权限固定下来,因此后续 Settings 变更只影响之后的会话。带 seed 或只完成部分初始化的会话会保留其有效调节项,只补齐缺失的事实;恢复时绝不会采用最新的用户默认值。`Session` 甚至会用 `session/end-seed` 标记显式为空的构造器 seed,因此不能把空的持久化日志误认为新会话。`permissions` 投影把该边界与三个权限事实一同折叠,使会话固定只读一份增量状态,无需重扫日志。
 
 现有 `/permission` 命令和 `permissions` 投影仍是当前会话的操作路径。浏览器插件现在向 `settings.general.item` 贡献「权限」行,从脱敏后的 Settings 描述符读取动态 enum,并只通过经过 revision 校验的 `settings.mutate` 写入 `defaultPreset`。该行通过 slot 的 `hooks` 格注入 observable,而不是绑定渲染器专用钩子;权限服务挂载时会遍历并固定所有已存活会话,因此 HMR(热模块替换)不会遗留未固定的会话。无归属的「通用」设置包不贡献任何占位行。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-07-31-web-default-search.i18n.yaml

@@ -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-31-web-default-search.md
-2026-07-31-web-default-search.md: dfd76176aa03741df38f60c6d30116f87ced4106
-2026-07-31-web-default-search.zh.md: 19fe20d7573accbcef45ab4db8c339335e2cc08b
+2026-07-31-web-default-search.md: eb0de16b5bf6133bbdb5275106f42eba75ddf607
+2026-07-31-web-default-search.zh.md: e1cc622e70d8af8e71a8c60aa7e7ceb9a2b91a5e

+ 1 - 1
.agents/notes/implemented/feature/2026-07-31-web-default-search.md

@@ -14,7 +14,7 @@ The harness had a complete Web capability family—provider registry, DeepSeek/E
 
 DeepSeek search uses the same `DEEPSEEK_API_KEY` credential reference as the official conversation adapter. The provider resolves that reference inside every search through the optional `ctx.credentials` service; only a composition without the seam falls back to the launching process environment, and a non-empty literal `apiKey` remains the programmatic last resort. A stored or rotated Web Models key therefore reaches the next search without restarting or retaining the value on the provider. Because `WebSearchProvider.available()` is synchronous, it treats an installed resolver as locally usable and missing dynamic credentials fail the operation with the provider-specific `WEB_PROVIDER_CREDENTIAL_MISSING` code while the stable tool schema stays registered.
 
-Search keeps its endpoint distinct from chat completions: `DEEPSEEK_SEARCH_BASE_URL` overrides the Anthropic-compatible base, while `DEEPSEEK_BASE_URL` continues to configure conversation requests. Each `web_search` performs an auxiliary DeepSeek Messages call with the native search server tool. Immediately before dispatch, the provider appends a log-only `web/deepseek-search-llm-request` event to the initiating Agent session with the resolved endpoint, API version, and exact secret-free JSON body. Credential preflight remains provider-local and races caller cancellation; neither concern expands the generic Web or credentials seams.
+Search keeps its endpoint distinct from chat completions: `DEEPSEEK_SEARCH_BASE_URL` overrides the Anthropic-compatible base, while `DEEPSEEK_BASE_URL` continues to configure conversation requests. Each `web_search` performs an auxiliary DeepSeek Messages call with the native search server tool. Immediately before dispatch, the provider appends a log-only `web/deepseek-search-llm-request` event to the initiating Agent session with the resolved endpoint, API version, and exact secret-free JSON body. A failure after dispatch names that endpoint and tells the conversation model to guide the user to the Web search Endpoint field in Settings when the endpoint is unintended. The message names `DEEPSEEK_SEARCH_BASE_URL` and `web-search-deepseek.baseURL` when that settings page is unavailable; the model does not select or change the credential destination. Credential preflight remains provider-local and races caller cancellation; neither concern expands the generic Web or credentials seams.
 
 The default mount does not create a Web-specific permission policy. `web_search` and enabled `web_fetch` calls execute outside the shell/filesystem sandbox and approval presets, following `dsh-tool-web`'s existing contract. The HTTP provider restricts fetches to validated public destinations, but it does not constrain public data egress. The shipped `workspace-write` default governs file mutations only; a restricted-network product stance requires a `tools/pre-execute` policy or capability-specific network confinement rather than implying that filesystem access mode governs Web calls.
 

+ 1 - 1
.agents/notes/implemented/feature/2026-07-31-web-default-search.zh.md

@@ -14,7 +14,7 @@ Status: implemented
 
 DeepSeek 搜索使用与官方会话适配器相同的 `DEEPSEEK_API_KEY` 凭据引用。提供方在每次搜索内部通过可选的 `ctx.credentials` 服务解析该引用;只有未挂载该 seam 的组合才会回退到启动进程的环境变量,非空的 `apiKey` 字面值仍作为程序化配置的最后兜底。因此,由 Web 的 Models 页存储或轮换的密钥无需重启即可用于下一次搜索,提供方也无需保留该值。由于 `WebSearchProvider.available()` 是同步方法,它会将已安装解析器视为本地可用;若动态凭据缺失,操作会以提供方专属错误码 `WEB_PROVIDER_CREDENTIAL_MISSING` 失败,而稳定的工具 schema 仍保持注册。
 
-搜索端点与 chat completions 保持独立:`DEEPSEEK_SEARCH_BASE_URL` 覆盖 Anthropic 兼容基址,`DEEPSEEK_BASE_URL` 则继续配置会话请求。每次 `web_search` 都会发起一次辅助 DeepSeek Messages 调用,并携带原生搜索服务器工具。发出请求前一刻,提供方会向发起请求的 agent(智能体)会话追加仅用于日志的 LLM(大语言模型)请求事件 `web/deepseek-search-llm-request`,其中包含已解析端点、API 版本,以及不含密钥的精确 JSON 请求体。凭据预检仍留在提供方内部,并与调用方取消存在竞态;这两项关注点都不会扩展通用 Web seam 或凭据 seam。
+搜索端点与 chat completions 保持独立:`DEEPSEEK_SEARCH_BASE_URL` 覆盖 Anthropic 兼容基址,`DEEPSEEK_BASE_URL` 则继续配置会话请求。每次 `web_search` 都会发起一次辅助 DeepSeek Messages 调用,并携带原生搜索服务器工具。发出请求前一刻,提供方会向发起请求的 agent(智能体)会话追加仅用于日志的 LLM(大语言模型)请求事件 `web/deepseek-search-llm-request`,其中包含已解析端点、API 版本,以及不含密钥的精确 JSON 请求体。请求发出后的失败会指出该端点;当端点不符合用户预期时,错误消息会要求会话模型指导用户在 Settings 中修改网页搜索的 Endpoint 字段。该设置页面不可用时,消息会说明 `DEEPSEEK_SEARCH_BASE_URL` 和 `web-search-deepseek.baseURL`;模型不得替用户选择或修改凭据发送目的地。凭据预检仍留在提供方内部,并与调用方取消存在竞态;这两项关注点都不会扩展通用 Web seam 或凭据 seam。
 
 默认挂载不会创建 Web 专用权限策略。`web_search` 与已启用的 `web_fetch` 调用会在 bash/文件系统沙箱及审批 preset 之外执行,并遵循 `dsh-tool-web` 的现有约定。HTTP 提供方把抓取限制到已验证的公开目的地址,但不限制公开数据出站。已交付的 `workspace-write` 默认值只管辖文件修改;若产品采取受限网络策略,就需要添加 `tools/pre-execute` 策略或按能力限制网络访问,而不能暗示文件系统访问模式会管辖 Web 调用。
 

+ 2 - 2
.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.i18n.yaml

@@ -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-08-06-continuable-subagent-interrupt.md
-2026-08-06-continuable-subagent-interrupt.md: dfc8df2b0e32defa97b1596bc8d95d8b34d8f65e
-2026-08-06-continuable-subagent-interrupt.zh.md: 3ac2ed34e50165a8958e4776e11f9cb863847276
+2026-08-06-continuable-subagent-interrupt.md: e7b2926cec32679fa66392381da6c0cb551fc324
+2026-08-06-continuable-subagent-interrupt.zh.md: 45f26a004819dad6212b4b901ddd7bc8b07bc306

+ 1 - 1
.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.md

@@ -45,4 +45,4 @@ The model-facing `interrupt_agent(agent_id)` tool in `dsh-tool-subagent-control`
 
 ## Testing
 
-Core coverage in `packages/subagent/subagent/tests/continuation.spec.ts` proves the durable `turn/end` abort, parked-then-FIFO-resumed queue, untouched descendant, both authority kinds with their cancel causes, self/sibling/stale/non-ancestor rejection, absent/one-shot/disposal-race no-ops, and the unchanged `keepInbox` loop behavior. Host coverage in `packages/api/gateway/tests/` proves the RPC calls only the core primitive (no agents/catalog/history reads), the `subagent-unauthorized`/`internal` mappings, the wire schema's continuable-mode fence, and carrier round-trips. Client coverage pins the address-routed `Session.cancel()`, the InputBar's independent Send and Stop actions with the parent-offline locked-input/Send state, and the read-only-composer selector's running exception; the keyless assembled Web scenarios (`apps/web/tests/subagent-interrupt.e2e.ts`, `subagent-interrupt-ui.e2e.ts`) hold real child turns open with replay hang entries and prove the parent-offline UI-to-RPC abort path, queued Send, the parked follow-up, and the FIFO resume end to end. Tool coverage in `packages/subagent/tool-subagent-control/tests` proves direct and deep ancestor interrupts with the `parent` cause and parked queue, self/sibling/stranger rejection without touching the target, absent-target no-ops without cold resume, and the descendants listing's pre-order positions; the keyless ACP snapshot executes `list_agents({ scope: 'descendants' })` and `interrupt_agent` through the assembled application against one settled child, while recorded request headers continue to pin both schemas.
+Core coverage in `packages/subagent/subagent/tests/continuation.spec.ts` proves the durable `turn/end` abort, parked-then-FIFO-resumed queue, untouched descendant, both authority kinds with their cancel causes, self/sibling/stale/non-ancestor rejection, absent/one-shot/disposal-race no-ops, and the unchanged `keepInbox` loop behavior. Remote coverage in `packages/subagent/subagent/tests/control.spec.ts` proves the Remote calls only the core primitive (no agents/catalog/history reads), maps `subagent-unauthorized` and `internal`, and enforces the continuable-mode fence; `packages/api/session-controller/tests/transport.host.spec.ts` pins transport validation. Client coverage in `packages/api/session-controller/tests/session.client.spec.ts` pins the address-routed `Session.cancel()`; the InputBar suites cover independent Send and Stop actions with the parent-offline locked-input/Send state, and the read-only-composer selector's running exception. The keyless assembled Web scenarios (`apps/web/tests/subagent-interrupt.e2e.ts`, `subagent-interrupt-ui.e2e.ts`) hold real child turns open with replay hang entries and prove the parent-offline UI-to-RPC abort path, queued Send, the parked follow-up, and the FIFO resume end to end. Tool coverage in `packages/subagent/tool-subagent-control/tests` proves direct and deep ancestor interrupts with the `parent` cause and parked queue, self/sibling/stranger rejection without touching the target, absent-target no-ops without cold resume, and the descendants listing's pre-order positions; the keyless ACP snapshot executes `list_agents({ scope: 'descendants' })` and `interrupt_agent` through the assembled application against one settled child, while recorded request headers continue to pin both schemas.

+ 1 - 1
.agents/notes/implemented/feature/2026-08-06-continuable-subagent-interrupt.zh.md

@@ -45,4 +45,4 @@ Host RPC `subagent.interrupt` 接收 continuable 的 `SubagentAddress` 并返回
 
 ## 测试
 
-`packages/subagent/subagent/tests/continuation.spec.ts` 中的核心覆盖证明了持久化 `turn/end` 中止、队列先暂停后按 FIFO 恢复、后代不受影响、两种授权及其取消 cause、self/sibling/stale/非 ancestor 拒绝、absent/一次性/disposal 竞态 no-op,以及 `keepInbox` 循环行为不变。`packages/api/gateway/tests/` 中的 Host 覆盖证明 RPC 只调用核心原语(不读 agents/目录/历史)、`subagent-unauthorized`/`internal` 映射、wire schema 的 continuable 模式围栏以及 carrier 往返。客户端覆盖固定按地址路由的 `Session.cancel()`、InputBar 的独立 Send 与 Stop 操作及 parent 离线时锁定输入区和 Send 的状态,以及只读 composer selector 的运行例外;keyless 组装 Web 场景(`apps/web/tests/subagent-interrupt.e2e.ts`、`subagent-interrupt-ui.e2e.ts`)通过多条 replay hang 条目保持多个真实 child 轮次打开,端到端证明 parent 离线时从 UI 到 RPC 的中止路径、Send 入队、follow-up 暂停以及 FIFO 恢复。`packages/subagent/tool-subagent-control/tests` 中的工具覆盖证明直接与更深 ancestor 以 `parent` cause 中断并暂停队列、self/sibling/陌生调用方被拒绝且不触碰目标、目标不存在时 no-op 且不冷恢复,以及 descendants 列表的 pre-order 位置;keyless ACP 快照通过组装应用,针对一个已结算的 child 执行 `list_agents({ scope: 'descendants' })` 与 `interrupt_agent`,同时已录制的请求 header 仍固定这两个 schema。
+`packages/subagent/subagent/tests/continuation.spec.ts` 中的核心覆盖证明了持久化 `turn/end` 中止、队列先暂停后按 FIFO 恢复、后代不受影响、两种授权及其取消 cause、self/sibling/stale/非 ancestor 拒绝、absent/一次性/disposal 竞态 no-op,以及 `keepInbox` 循环行为不变。`packages/subagent/subagent/tests/control.spec.ts` 中的 Remote 覆盖证明 Remote 只调用核心原语(不读 agents/目录/历史)、映射 `subagent-unauthorized` 与 `internal`,并执行 continuable 模式围栏;`packages/api/session-controller/tests/transport.host.spec.ts` 固定传输校验。`packages/api/session-controller/tests/session.client.spec.ts` 中的客户端覆盖固定按地址路由的 `Session.cancel()`;InputBar 套件覆盖独立的 Send 与 Stop 操作、parent 离线时锁定输入区和 Send 的状态,以及只读 composer selector 的运行例外。keyless 组装 Web 场景(`apps/web/tests/subagent-interrupt.e2e.ts`、`subagent-interrupt-ui.e2e.ts`)通过多条 replay hang 条目保持多个真实 child 轮次打开,端到端证明 parent 离线时从 UI 到 RPC 的中止路径、Send 入队、follow-up 暂停以及 FIFO 恢复。`packages/subagent/tool-subagent-control/tests` 中的工具覆盖证明直接与更深 ancestor 以 `parent` cause 中断并暂停队列、self/sibling/陌生调用方被拒绝且不触碰目标、目标不存在时 no-op 且不冷恢复,以及 descendants 列表的 pre-order 位置;keyless ACP 快照通过组装应用,针对一个已结算的 child 执行 `list_agents({ scope: 'descendants' })` 与 `interrupt_agent`,同时已录制的请求 header 仍固定这两个 schema。

+ 2 - 2
.agents/notes/implemented/process/2026-06-11-quality-gates.i18n.yaml

@@ -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/process/2026-06-11-quality-gates.md
-2026-06-11-quality-gates.md: 83cba5c867f71471d421ab935e1f53b430b01c43
-2026-06-11-quality-gates.zh.md: 10ae488f99afbb67fe1f5ffda0b95f633226a137
+2026-06-11-quality-gates.md: 6a66fc6a4ead8908bfaf3a417568a3a7eb458744
+2026-06-11-quality-gates.zh.md: d546a1bbbac48afdbae6f1218da8f4051df2a1bc

+ 1 - 1
.agents/notes/implemented/process/2026-06-11-quality-gates.md

@@ -18,7 +18,7 @@ Every mechanically checkable AGENTS.md promise gets a command that exits non-zer
 - [Oxlint](2026-07-29-oxlint-linter.md) with type-aware TypeScript rules plus the @stylistic and SonarJS compatibility plugins, enforcing the house style and file-local duplicated-logic checks; vendored code excluded.
 - jscpd detects cross-file clones in package production TypeScript and repository scripts; narrow source-range exceptions document deliberately parallel implementations.
 - Per-file 100% coverage on `packages/*/*/src` (v8); unreachable defensive guards carry `/* v8 ignore */ ` with stated reasons instead of deletion.
-- knip (dead code/deps), publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM), and a NodeNext consumer typecheck for built package declarations.
+- publint (package correctness), workspace constraints (workspace rules: private, cordis peer+dev, uniform version, ESM), and a NodeNext consumer typecheck for built package declarations. [The unused-code gate removal](2026-08-19-remove-knip.md) records why static dead-code scanning is outside this suite.
 - lefthook pre-commit applies project-free Oxlint validation and [safe fixes with a bounded retry](2026-08-09-oxlint-only-fix-workflow.md), rejects staged whitespace, and checks the vendor manifest; pre-push runs incremental typecheck. CI runs the full matrix on node 22.19/24/26 plus built application smokes for the Headless, TUI, ACP, JSON-RPC, workflow, and code-runtime entry paths.
 
 ## Consequences

+ 1 - 1
.agents/notes/implemented/process/2026-06-11-quality-gates.zh.md

@@ -18,7 +18,7 @@ Status: implemented
 - [Oxlint](2026-07-29-oxlint-linter.zh.md) 配合类型感知的 TypeScript 规则以及 @stylistic 和 SonarJS 兼容插件,强制执行统一代码风格和文件内重复逻辑检查;vendor 代码排除在外。
 - jscpd 检测包的生产 TypeScript 代码与仓库脚本中的跨文件克隆;窄范围的源码区间例外用于记录有意为之的并行实现。
 - `packages/*/*/src` 下按文件 100% 覆盖率(v8);不可达的防御性守卫使用 `/* v8 ignore */ ` 并注明理由,而非删除。
-- knip(死代码/依赖)、publint(包的正确性)、workspace 约束(workspace 规则:private、cordis peer+dev、统一版本、ESM),以及对构建出的包声明文件进行 NodeNext 消费方类型检查。
+- publint(包的正确性)、workspace 约束(workspace 规则:private、cordis peer+dev、统一版本、ESM),以及对构建出的包声明文件进行 NodeNext 消费方类型检查。[移除未使用代码门禁的决策](2026-08-19-remove-knip.zh.md)说明了静态死代码扫描为何不属于这套门禁。
 - lefthook pre-commit 执行不加载项目的 Oxlint 验证,并应用带[一次有界重试](2026-08-09-oxlint-only-fix-workflow.zh.md)的安全修复,拒绝已暂存的空白问题并检查 vendor manifest(元数据清单);pre-push 运行增量类型检查。CI 在 Node 22.19/24/26 上运行完整矩阵,并对 Headless、TUI、ACP(Agent Client Protocol)、JSON-RPC、工作流和代码运行时入口路径执行已构建应用的冒烟测试。
 
 ## 后果

+ 2 - 2
.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.i18n.yaml

@@ -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/process/2026-06-16-pnpm-over-yarn.md
-2026-06-16-pnpm-over-yarn.md: e2d207239fe93c3de265e595d2a9bb4523567169
-2026-06-16-pnpm-over-yarn.zh.md: 3dd735bd132816ae9cdc4ebda11c4227b89a88ba
+2026-06-16-pnpm-over-yarn.md: 4a8560a0216e614f4ea06c0a1aac3a553abcaf8d
+2026-06-16-pnpm-over-yarn.zh.md: 4955cf88834a56d89ba1c4c1866c72cf647ccbd3

+ 2 - 2
.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md

@@ -15,7 +15,7 @@ The switching cost is at its lowest right now. Nothing publishes from this repo
 Adopt **pnpm 11.7.0**, pinned via the `packageManager` field. Contributor setup uses Corepack, while CI installs that pin through `pnpm/action-setup`:
 
 - **Workspaces** move from the `package.json` `workspaces` array + `.yarnrc.yml` to `pnpm-workspace.yaml`; vendored packages, grouped packages, applications, the website, native launchers, and the Python runtime closure are explicit members.
-- **Strict symlinked linker** (pnpm's default) replaces Yarn's hoisted `node-modules` linker. We deliberately add **no** `node-linker=hoisted` / `shamefully-hoist` escape hatch: pnpm's non-flat `node_modules` makes phantom dependencies (importing an undeclared transitive dep) fail loudly, which is a *feature* for a repo whose whole quality story is mechanical gates ([mechanical quality gates](2026-06-11-quality-gates.md)). The gate suite — typecheck, lint, test, build, knip — is the safety net that proves no such phantom imports exist.
+- **Strict symlinked linker** (pnpm's default) replaces Yarn's hoisted `node-modules` linker. We deliberately add **no** `node-linker=hoisted` / `shamefully-hoist` escape hatch: pnpm's non-flat `node_modules` makes phantom dependencies (importing an undeclared transitive dep) fail loudly, which is a *feature* for a repo whose whole quality story is mechanical gates ([mechanical quality gates](2026-06-11-quality-gates.md)). The gate suite — typecheck, lint, test, and build — is the safety net that proves no such phantom imports exist.
 - **Build-script allowlist.** pnpm 10+ does not run dependency lifecycle scripts unless allowlisted. `pnpm-workspace.yaml` carries an explicit `allowBuilds` map (`esbuild`, `lefthook`, `@google/genai`, `protobufjs`) — the same supply-chain-hardening posture the repo already takes toward model/tool output, now applied to install-time code execution. `peerDependencyRules.allowedVersions.typescript: '>=5 <7'` silences benign peer-range warnings for the in-repo TypeScript.
 - **Shell-free package-manager re-entry.** Repository scripts that start another pnpm command resolve `npm_execpath` by file form: `.js`, `.cjs`, and `.mjs` entries run under the current Node executable, while native and shebang executables run directly. Neither path uses a shell, so command paths and arguments retain their literal contents across platforms. The [native Windows pull-request job](2026-08-08-native-windows-pull-request-ci.md) provisions `@pnpm/exe`, so its complete inventory supplies a real PE-entry integration signal.
 - **Constraints become package-manager-independent.** `yarn.config.cjs` (which imported `@yarnpkg/types` and used `Yarn.workspaces()` / `workspace.set()`) is replaced by `scripts/check-workspace-constraints.ts`, a plain tsx script run as `pnpm run constraints`. It enforces the identical invariants — every package `private: true`; `@deepseek-ai/dsh-*` packages declare `cordis` as both a peer- and dev-dependency with matching ranges, use the root `package.json` version, and set `type: module`; vendored packages checked for privacy only — over the same `vendor` + `packages` scope.
@@ -43,4 +43,4 @@ Performance (measured at migration time on the dev NFS filesystem; single-digit-
 
 On a fast local disk pnpm's content-addressed store typically wins on cold/warm installs and, especially, on **disk footprint** across multiple checkouts (one global store hardlinked into every `node_modules` vs Yarn copying ~279 MB per worktree — some devs regularly keep ~10 or more worktrees for this repo). That dedup advantage did **not** show in the migration-time numbers above because the test store and `node_modules` sat on different filesystems, defeating hardlinks; on a single-filesystem dev box or CI cache it applies. The honest summary: install speed on our NFS dev filesystem is a wash within noise; the move is justified by ecosystem alignment, phantom-dependency safety, and cross-checkout disk dedup — not by a raw install-time win.
 
-All quality gates (constraints, typecheck, lint, doc-sync, test:coverage at 100%, build, knip, publint, and built application smokes) pass on pnpm, which is the correctness proof that the linker swap introduces no phantom-dependency breakage.
+All quality gates (constraints, typecheck, lint, doc-sync, test:coverage at 100%, build, publint, and built application smokes) pass on pnpm, which is the correctness proof that the linker swap introduces no phantom-dependency breakage.

+ 2 - 2
.agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.zh.md

@@ -15,7 +15,7 @@ Status: implemented
 采用 **pnpm 11.7.0**,通过 `packageManager` 字段固定版本。贡献者环境使用 Corepack,CI 则通过 `pnpm/action-setup` 安装该固定版本:
 
 - **Workspaces** 从 `package.json` 的 `workspaces` 数组 + `.yarnrc.yml` 迁移到 `pnpm-workspace.yaml`;vendored 包、分组包、应用、网站、原生 launcher 与 Python runtime closure 都是显式成员。
-- **严格符号链接链接器**(pnpm 默认)取代 Yarn 的提升式 `node-modules` 链接器。我们刻意**不**添加 `node-linker=hoisted` / `shamefully-hoist` 逃生口:pnpm 的非扁平 `node_modules` 会使幻影依赖(引用未声明的传递依赖)明确报错,这对于一个以机械门禁为核心质量保障的仓库(见[机械质量门禁](2026-06-11-quality-gates.zh.md))是一项*优势*。门禁套件(类型检查、lint、test、build、knip)是证明不存在此类幻影导入的安全网。
+- **严格符号链接链接器**(pnpm 默认)取代 Yarn 的提升式 `node-modules` 链接器。我们刻意**不**添加 `node-linker=hoisted` / `shamefully-hoist` 逃生口:pnpm 的非扁平 `node_modules` 会使幻影依赖(引用未声明的传递依赖)明确报错,这对于一个以机械门禁为核心质量保障的仓库(见[机械质量门禁](2026-06-11-quality-gates.zh.md))是一项*优势*。门禁套件(类型检查、lint、test 和 build)是证明不存在此类幻影导入的安全网。
 - **构建脚本白名单。** pnpm 10+ 不运行依赖的生命周期脚本,除非将其加入白名单。`pnpm-workspace.yaml` 携带一份显式的 `allowBuilds` 映射(`esbuild`、`lefthook`、`@google/genai`、`protobufjs`)——与本仓库对模型/工具输出已有的供应链加固姿态一致,现在也应用于安装时的代码执行。`peerDependencyRules.allowedVersions.typescript: '>=5 <7'` 消除仓库内 TypeScript 的良性 peer 范围警告。
 - **无 shell 的包管理器再进入。** 需要启动另一条 pnpm 命令的仓库脚本按文件形式解析 `npm_execpath`:`.js`、`.cjs` 和 `.mjs` 入口由当前 Node 可执行文件运行,原生及带 shebang 的可执行入口则直接运行。两条路径都不使用 shell,因此命令路径和参数在各平台上均保留字面内容。[原生 Windows 拉取请求作业](2026-08-08-native-windows-pull-request-ci.zh.md)会提供 `@pnpm/exe`,因此其完整清单会产生真实的 PE 入口集成信号。
 - **约束变为包管理器无关。** `yarn.config.cjs`(导入 `@yarnpkg/types`,使用 `Yarn.workspaces()` / `workspace.set()`)被 `scripts/check-workspace-constraints.ts` 取代——一个纯 tsx 脚本,通过 `pnpm run constraints` 运行。它在相同的 `vendor` + `packages` 范围上强制执行完全相同的不变式:每个包 `private: true`;`@deepseek-ai/dsh-*` 包将 `cordis` 同时声明为对等依赖(peer dependency)和 dev 依赖且范围一致、使用根 `package.json` 的版本、设置 `type: module`;vendor 包仅检查是否为私有。
@@ -43,4 +43,4 @@ Status: implemented
 
 在快速本地磁盘上,pnpm 的内容寻址 store 通常在冷/热安装中胜出,尤其在多个检出之间的**磁盘占用**方面优势明显(一个全局 store 通过硬链接接入每个 `node_modules`,而 Yarn 每个 worktree 复制约 279 MB——部分开发者经常为本仓库保持约 10 个或更多 worktree)。该去重优势在上述迁移时数据中**未能**体现,因为测试 store 和 `node_modules` 位于不同文件系统,硬链接失效;在单文件系统的开发机或 CI 缓存上则适用。诚实的总结:在我们的 NFS 开发文件系统上,安装速度在噪声范围内不分伯仲;迁移的理由是生态对齐、幻影依赖安全性和跨检出磁盘去重,而非原始安装时间的胜出。
 
-所有质量门禁(constraints、类型检查、lint、doc-sync、达到 100% 的 test:coverage、构建、knip、publint 以及已构建应用的冒烟测试)均在 pnpm 下通过,证明更换链接器没有引入幻影依赖故障。
+所有质量门禁(constraints、类型检查、lint、doc-sync、达到 100% 的 test:coverage、构建、publint 以及已构建应用的冒烟测试)均在 pnpm 下通过,证明更换链接器没有引入幻影依赖故障。

+ 2 - 2
.agents/notes/implemented/process/2026-07-13-documentation-site-projection.i18n.yaml

@@ -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/process/2026-07-13-documentation-site-projection.md
-2026-07-13-documentation-site-projection.md: eaaf7cfc50b5aba348533ea4f5a4a1f50641f30f
-2026-07-13-documentation-site-projection.zh.md: c954fcc41c874007330a66b93cea50fef01f8e3e
+2026-07-13-documentation-site-projection.md: 05e76963ec6cecd6654afeda410334945a893f69
+2026-07-13-documentation-site-projection.zh.md: c3e9bafac1b6b397b111d059f345b49a51a1fc37

+ 1 - 1
.agents/notes/implemented/process/2026-07-13-documentation-site-projection.md

@@ -24,7 +24,7 @@ The projector parses Markdown links without reserializing the document. A link t
 
 `website/AGENTS.md` is the only maintained Markdown file in the website subtree. The projector test enumerates tracked and unignored files and rejects any other website Markdown, so site-specific locale, route, API, or generated source copies cannot bypass the publication manifest.
 
-Mermaid renders the canonical diagrams. The website workspace explicitly declares the five packages that `vitepress-plugin-mermaid` asks Vite to prebundle because pnpm's strict dependency isolation otherwise makes those transitive packages unavailable to the local development server; Knip records this runtime-only use as an intentional dependency exception.
+Mermaid renders the canonical diagrams. The website workspace explicitly declares the five packages that `vitepress-plugin-mermaid` asks Vite to prebundle because pnpm's strict dependency isolation otherwise makes those transitive packages unavailable to the local development server.
 
 The configured Markdown renderer keeps a build-local cache for non-Mermaid, non-snippet code fences, keyed by the exact content, info string, delimiter, and token attributes. About half of the bilingual projection's code fences repeat, so Shiki renders each distinct representation once. Mermaid fences bypass the cache because the plugin output includes a token-position id, and VitePress snippets resolve their source files during rendering. The cache is enabled only for production builds, is discarded with the renderer after each build, and does not change the emitted fence HTML.
 

+ 1 - 1
.agents/notes/implemented/process/2026-07-13-documentation-site-projection.zh.md

@@ -24,7 +24,7 @@ Status: implemented
 
 `website/AGENTS.md` 是网站子树中唯一维护的 Markdown 文件。投影器测试会枚举所有已跟踪文件和未被忽略的未跟踪文件,并拒绝网站中的任何其他 Markdown,因此网站专用的 locale、路由、API 或生成源文件副本无法绕过发布 manifest。
 
-Mermaid 渲染权威图表。网站工作区显式声明 `vitepress-plugin-mermaid` 要求 Vite 预打包的 5 个包,因为 pnpm 的严格依赖隔离会使本地开发服务器无法使用这些传递依赖;Knip 将这种仅运行时使用记录为有意的依赖例外。
+Mermaid 渲染权威图表。网站工作区显式声明 `vitepress-plugin-mermaid` 要求 Vite 预打包的 5 个包,因为 pnpm 的严格依赖隔离会使本地开发服务器无法使用这些传递依赖。
 
 配置后的 Markdown 渲染器会在单次构建期间缓存非 Mermaid、非 snippet 代码围栏,缓存键包含确切正文、info string、分隔符和 token 属性。双语投影中约半数代码围栏是重复内容,因此 Shiki 只渲染一次每种不同表示。Mermaid 围栏不会进入缓存,因为插件输出包含 token 位置 id;VitePress snippet 则在渲染期间解析源文件。缓存仅在生产构建中启用,每次构建后会随渲染器一同丢弃,并且不会改变生成的围栏 HTML。
 

+ 2 - 2
.agents/notes/implemented/process/2026-07-20-gui-testing-system.i18n.yaml

@@ -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/process/2026-07-20-gui-testing-system.md
-2026-07-20-gui-testing-system.md: 41cf4fa93b63c4cf9bf9e1327983d36c0c98ebfb
-2026-07-20-gui-testing-system.zh.md: 603124368e49cab8755a9c93885e85b2e288b0a3
+2026-07-20-gui-testing-system.md: fa38b6e57fc0ff48f2604b000f6b9131897f2e4c
+2026-07-20-gui-testing-system.zh.md: 06ff9b81aad0d13abf51b2db093670d5e0ca57fb

+ 2 - 2
.agents/notes/implemented/process/2026-07-20-gui-testing-system.md

@@ -2,7 +2,7 @@
 
 Status: implemented
 
-> Path update (2026-08-23, Controller split): the three-tier philosophy and golden-path method here remain current; object-layer specs now live across `packages/api/session-controller/tests/` and `packages/test-support/client-runtime/tests/`, while wire specs remain in `packages/client/connection/tests/`. Component specs are per-plugin jsdom suites under each `packages/client/*/tests/`. Component-spec shape follows the [slot system standard](../architecture/2026-07-22-slot-type-chain-implementation.md): feed props directly — the store share comes from `createXXXStore().create()` (the real engine, the sanctioned zero-machinery path), framework hooks are plain stubs; no render machinery, no provider mounting. Slot ownership and registry semantics are tier-2 territory (`ui-renderer` + `ui-slots` suites), not component specs.
+> Path update (2026-08-27, Remote migration): the three-tier philosophy and golden-path method here remain current; object-layer specs live across `packages/api/session-controller/tests/` and `packages/test-support/client-runtime/tests/`, while Remote and carrier specs live across `packages/api/gateway/tests/` and `packages/client/connection/tests/`. Component specs are per-plugin jsdom suites under each `packages/client/*/tests/`. Component-spec shape follows the [slot system standard](../architecture/2026-07-22-slot-type-chain-implementation.md): feed props directly — the store share comes from `createXXXStore().create()` (the real engine, the sanctioned zero-machinery path), framework hooks are plain stubs; no render machinery, no provider mounting. Slot ownership and registry semantics are tier-2 territory (`ui-renderer` + `ui-slots` suites), not component specs.
 
 English | [中文](2026-07-20-gui-testing-system.zh.md)
 
@@ -18,7 +18,7 @@ Cut along the architecture's natural test hooks into three tiers, bottom-up:
 
 | Tier | Under test | Key technique | File location |
 |---|---|---|---|
-| 1 Protocol isomorphism | `AbstractApiClient` + `toFetchHandler` (bidirectional data / rpcId / zod types / SSE streams / batching / timeouts) | **The full chain at the isomorphic point**: `InProcessApiClient(toFetchHandler(脚本化 impl))` skips the network but genuinely runs the wire serialization — zero browser, pure node env | `packages/client/connection/tests/` |
+| 1 Protocol isomorphism | Generated Typert Remote descriptors + `ApiGateway` + the Connection RPC carrier (arguments / results / errors / streams / cancellation) | **The full chain at the isomorphic point**: gateway host/client suites validate descriptor codecs and Remote dispatch in process; Connection host suites exercise the same `/api` carrier framing and trust checks without a browser | `packages/api/gateway/tests/`, `packages/client/connection/tests/` |
 | 2 Object-layer orchestration | `Session`/`SessionManager`/`ConnectionController` (state machines and timing: stitching / dedup / paging / optimistic draft clearing / pendingBuffers / reconnect / backoff) | **The "event sequence in → snapshot out" golden path**: programmable fakes + deferreds controlling timing + fake timers controlling backoff | `packages/client/{runtime,connection}/tests/` |
 | 3 Assembled presentation | Built artifacts × the real client loader and plugin composition | App-owned semantic snapshots boot all eight built client plugins under jsdom for deterministic cross-plugin state changes; bare Playwright smoke separately proves the real browser/carrier boundary, with real-host cases self-skipping without a key; the keyless browser e2e lane disables the shipped model-adapter row and replays recorded session fixtures through `dsh-llm-replay` in the real in-process web assembly against conversation aria goldens ([web e2e lane](../testing/2026-07-24-web-gui-browser-e2e-lane.md), [required CI gate](../testing/2026-07-30-web-browser-snapshot-ci-gate.md)) | `apps/web/tests/*.snapshot.ts`, `apps/web/tests/smoke-{fixture,real}.e2e.ts`, `apps/web/tests/{replay-round-trip,seeded-history}.e2e.ts` |
 

+ 2 - 2
.agents/notes/implemented/process/2026-07-20-gui-testing-system.zh.md

@@ -2,7 +2,7 @@
 
 Status: implemented
 
-> 路径更新(2026-08-23,Controller 拆分):本文三层理念与黄金路径方法仍为现行;对象层 spec 现分布于 `packages/api/session-controller/tests/` 和 `packages/test-support/client-runtime/tests/`,wire spec 仍位于 `packages/client/connection/tests/`。组件 spec 是各 `packages/client/*/tests/` 下的插件级 jsdom 套件。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.zh.md):props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的无额外机制路径),框架钩子用普通桩;无渲染机制、不挂载提供方。slot 归属和注册表语义归 2 层地界(`ui-renderer` + `ui-slots` 套件),不归组件 spec。
+> 路径更新(2026-08-27,Remote 迁移):本文三层理念与黄金路径方法仍为现行;对象层 spec 分布于 `packages/api/session-controller/tests/` 和 `packages/test-support/client-runtime/tests/`,Remote 与承载 spec 分布于 `packages/api/gateway/tests/` 和 `packages/client/connection/tests/`。组件 spec 是各 `packages/client/*/tests/` 下的插件级 jsdom 套件。组件 spec 形态遵循 [slot 体系标准](../architecture/2026-07-22-slot-type-chain-implementation.zh.md):props 直喂——store 份额来自 `createXXXStore().create()`(真引擎,获认可的无额外机制路径),框架钩子用普通桩;无渲染机制、不挂载提供方。slot 归属和注册表语义归 2 层地界(`ui-renderer` + `ui-slots` 套件),不归组件 spec。
 
 [English](2026-07-20-gui-testing-system.md) | 中文
 
@@ -18,7 +18,7 @@ GUI 栈需要考虑多种应用形态,同应用形态内的不同运行环境
 
 | 层 | 被测物 | 关键手段 | 文件落点 |
 |---|---|---|---|
-| 1 协议同构层 | `AbstractApiClient` + `toFetchHandler`(双向数据/rpcId/ZOD 类型/SSE(Server-Sent Events)流/合批/超时) | **同构点全链**:`InProcessApiClient(toFetchHandler(脚本化 impl))` 不过网络但真跑 wire 序列化——零浏览器、纯 node env | `packages/client/connection/tests/` |
+| 1 协议同构层 | 生成的 Typert Remote 描述符 + `ApiGateway` + Connection RPC 承载(参数/结果/错误/流/取消) | **同构点全链**:gateway host/client 套件在进程内验证描述符 codec 与 Remote 分派;Connection host 套件不经浏览器即可运行相同的 `/api` 承载帧与信任检查 | `packages/api/gateway/tests/`、`packages/client/connection/tests/` |
 | 2 对象层编排 | `Session`/`SessionManager`/`ConnectionController`(状态机与时序:缝合/去重/翻页/乐观清稿/pendingBuffers/重连/退避) | **「事件序列进→快照出」黄金路径**:可编程假体 + deferred 控时序 + fake timers 控退避 | `packages/client/{runtime,connection}/tests/` |
 | 3 组装呈现层 | 构建产物 × 真实 client loader 与插件组合 | 归应用所有的语义快照会在 jsdom 下启动全部 8 个已构建的 client 插件,以确定性方式驱动跨插件状态变化;另有最简 Playwright 冒烟测试负责验证真实浏览器/承载层边界,真 host 用例在无密钥时自行跳过;无密钥浏览器 e2e 车道会禁用交付配置中的模型适配器行,并通过 `dsh-llm-replay` 在真实进程内 web 组装中回放录制的会话 fixture(测试前置数据),与会话区 aria 预期输出比对([web e2e 车道](../testing/2026-07-24-web-gui-browser-e2e-lane.zh.md)、[必需 CI 门禁](../testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md)) | `apps/web/tests/*.snapshot.ts`、`apps/web/tests/smoke-{fixture,real}.e2e.ts`、`apps/web/tests/{replay-round-trip,seeded-history}.e2e.ts` |
 

+ 2 - 2
.agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.i18n.yaml

@@ -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/process/2026-07-30-independent-ci-consumer-build.md
-2026-07-30-independent-ci-consumer-build.md: ea87d8051a30c282bdf57ddc3226072be8cb7f24
-2026-07-30-independent-ci-consumer-build.zh.md: d640aaab54147fb0fc7a37005d9989bc4d67434c
+2026-07-30-independent-ci-consumer-build.md: a6d8379579714af4f66807c0110ce17ca3604f30
+2026-07-30-independent-ci-consumer-build.zh.md: a48553564c55920f1748bef23e9cfd867fa93e6c

+ 1 - 1
.agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.md

@@ -6,7 +6,7 @@ English | [中文](2026-07-30-independent-ci-consumer-build.zh.md)
 
 ## Problem
 
-The [larger-runner topology](2026-07-22-evidence-based-larger-hosted-runners.md) gave the static and built-consumer inventories separate jobs, but the static job owned their shared build. It uploaded the emitted tree only after every static gate completed, and the consumer job declared a job-level dependency before restoring that tree. Compiled-output snapshots and publication checks genuinely require a complete build; they do not require runtime-closure checks, documentation generation, module-graph verification, or Knip.
+The [larger-runner topology](2026-07-22-evidence-based-larger-hosted-runners.md) gave the static and built-consumer inventories separate jobs, but the static job owned their shared build. It uploaded the emitted tree only after every static gate completed, and the consumer job declared a job-level dependency before restoring that tree. Compiled-output snapshots and publication checks genuinely require a complete build; they do not require runtime-closure checks, documentation generation, or module-graph verification.
 
 That wider dependency made runner availability part of the required critical chain. In one failover run, static waited 8 minutes 1 second for a runner and ran for 1 minute 41 seconds; only then could consumers enter the same shared pool, where they waited another 10 minutes 34 seconds before running for 1 minute 58 seconds. Reusing the static build saved repository work but serialized two independent runner allocations.
 

+ 1 - 1
.agents/notes/implemented/process/2026-07-30-independent-ci-consumer-build.zh.md

@@ -6,7 +6,7 @@ Status: implemented
 
 ## 问题
 
-[大型运行器拓扑](2026-07-22-evidence-based-larger-hosted-runners.zh.md)将静态门禁清单和构建后消费方清单分配给不同作业,但二者共用的构建由静态作业负责。静态作业要等所有静态门禁完成后才上传生成的目录树,消费方作业则在恢复该目录树前声明了作业级依赖。基于编译输出的快照与发布校验确实需要完整构建,但不依赖运行时依赖闭包检查、文档生成、模块图验证或 Knip。
+[大型运行器拓扑](2026-07-22-evidence-based-larger-hosted-runners.zh.md)将静态门禁清单和构建后消费方清单分配给不同作业,但二者共用的构建由静态作业负责。静态作业要等所有静态门禁完成后才上传生成的目录树,消费方作业则在恢复该目录树前声明了作业级依赖。基于编译输出的快照与发布校验确实需要完整构建,但不依赖运行时依赖闭包检查、文档生成或模块图验证。
 
 这项过宽的依赖使运行器可用性成为必需关键链的一环。一次故障切换运行中,静态作业等待运行器 8 分 1 秒,随后运行 1 分 41 秒;直到此时,消费方作业才能进入同一个共享池,它又等待 10 分 34 秒,随后运行 1 分 58 秒。复用静态作业的构建省去了部分仓库工作,却让两次原本相互独立的运行器分配串行发生。
 

+ 2 - 2
.agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.i18n.yaml

@@ -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/process/2026-08-06-coverage-uncovered-locations.md
-2026-08-06-coverage-uncovered-locations.md: 7a4bd7317bf66d090d73a6a05f361ccc1582f1c4
-2026-08-06-coverage-uncovered-locations.zh.md: 12560dd9297076746d58f78f5ce3538346082ac8
+2026-08-06-coverage-uncovered-locations.md: d4036ab17ac2d4aa6a0428fcbbdd3fc6d7e6968d
+2026-08-06-coverage-uncovered-locations.zh.md: 6a10efd216501cb45837be2f7505531c398bf6cd

+ 1 - 1
.agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.md

@@ -21,7 +21,7 @@ Output conventions:
 - An implicit branch arm (such as a missing else) may carry no location; the reporter falls back to the branch's own span so the record stays clickable; branch records are annotated with the branch type and `path k/n`.
 - Records within a file are sorted by line, then column; there is no cap on the count.
 
-Two companion changes: the root `package.json` adds `istanbul-lib-report` as a devDependency (under pnpm's strict layout, `scripts/` cannot reach nested dependencies); the root workspace's entry/project globs in `knip.json` gain `scripts/**/*.cjs`, making the file and its dependencies visible to the hygiene gate.
+The root `package.json` declares `istanbul-lib-report` as a devDependency because pnpm's strict layout prevents `scripts/` from reaching nested dependencies.
 
 CJS is a forced shape, and a justified exception to the ESM-everywhere discipline: istanbul loads custom reporters via a bare `require()` outside the tsx/Vite pipeline, where TypeScript cannot participate; the namespace object `require(esm)` returns also fails its `new Cons(cfg)` construction — CommonJS is the only reliable shape.
 

+ 1 - 1
.agents/notes/implemented/process/2026-08-06-coverage-uncovered-locations.zh.md

@@ -21,7 +21,7 @@ per-file 100% 覆盖率门禁失败时,vitest 只输出文件级错误行(`E
 - 隐式分支臂(如缺少 else 的情况)可能不带位置,reporter 会回退到分支自身的 span,保证记录仍可点击;分支记录标注类型与 `path k/n`。
 - 同文件内记录按行、列排序;不设条数上限。
 
-配套两处:根 `package.json` 增补 devDependency `istanbul-lib-report`(pnpm 严格布局下 `scripts/` 摸不到嵌套依赖);`knip.json` 根 workspace 的 entry/project 通配增加 `scripts/**/*.cjs`,使该文件及其依赖对 hygiene 门禁可见。
+根 `package.json` 将 `istanbul-lib-report` 声明为 devDependency,因为 pnpm 的严格布局使 `scripts/` 无法访问嵌套依赖。
 
 CJS 是被迫的形态,也是 ESM-everywhere 纪律的一个有据例外:istanbul 在 tsx/Vite 流水线之外用裸 `require()` 装载自定义 reporter,TypeScript 无法参与;`require(esm)` 返回的命名空间对象也过不了它的 `new Cons(cfg)` 构造,CommonJS 是唯一可靠形态。
 

+ 2 - 2
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.i18n.yaml

@@ -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/process/2026-08-08-native-windows-pull-request-ci.md
-2026-08-08-native-windows-pull-request-ci.md: 8faec10fdc8538c994954213ac477a2ed52e800c
-2026-08-08-native-windows-pull-request-ci.zh.md: f692aa85e33e2e02d04cd72d9316ed4fc139aced
+2026-08-08-native-windows-pull-request-ci.md: cf656ab2c82fb43b4172a07b6b68fa53a62ef8a3
+2026-08-08-native-windows-pull-request-ci.zh.md: 81ed390d27d7f7918c540a56a2d1fac1c094a447

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.zh.md


+ 2 - 2
.agents/notes/implemented/process/2026-08-10-vendor-package-rescope.i18n.yaml

@@ -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/process/2026-08-10-vendor-package-rescope.md
-2026-08-10-vendor-package-rescope.md: 3ad442a681229b7b9df8feb1c487a925b6123f23
-2026-08-10-vendor-package-rescope.zh.md: 276c94ac9e7d755c02e325f4a4657e21a8bd6ab9
+2026-08-10-vendor-package-rescope.md: 5b5bc186bd01d0509d127a0a866da99509498c69
+2026-08-10-vendor-package-rescope.zh.md: 66aab8770940a38d573bca578b003ca87ae4cfd5

+ 1 - 1
.agents/notes/implemented/process/2026-08-10-vendor-package-rescope.md

@@ -34,7 +34,7 @@ Markdown splits along what a reader does with it. Every fence follows the rename
 
 - No upstream name remains in the publication set. `publish-npm-baseline.ts` now requires every published package to be `@deepseek-ai/*` with no vendored exemption, so regressing the rename fails before packing.
 - The `vendor/README.md` manifest table gains an upstream-name column; `gen-third-party-notices` parses six columns and renders that name into `THIRD_PARTY_NOTICES.md`, keeping MIT attribution pointed at each fork's origin rather than our scope.
-- `pnpm-workspace.yaml` drops the `cordis` and `@cordisjs/plugin-loader` `minimumReleaseAgeExclude` entries, which can no longer be fetched from a registry, and `knip.json` drops the `@cordisjs/.+` ignore pattern that `@deepseek-ai/.+` already covers.
+- `pnpm-workspace.yaml` drops the `cordis` and `@cordisjs/plugin-loader` `minimumReleaseAgeExclude` entries, which can no longer be fetched from a registry.
 - Upstream sync follows the procedure in `vendor/README.md` with one added obligation in step 3: re-apply the rename over the copied sources with `pnpm run rescope-vendor --apply`, whose mapping and the table's two name columns must agree.
 - **Returning to the official upstream packages** means applying that mapping in reverse — `pnpm run rescope-vendor --apply --reverse` — then restoring the two `minimumReleaseAgeExclude` entries and relaxing the publication-set assertion. It spans roughly 1300 files, so replay it with the script rather than by hand.
 

+ 1 - 1
.agents/notes/implemented/process/2026-08-10-vendor-package-rescope.zh.md

@@ -34,7 +34,7 @@ Markdown 按「读者拿它做什么」一分为二。围栏一律跟着改,
 
 - 发布集里不再有任何上游名:`publish-npm-baseline.ts` 现在无条件要求每个待发包都是 `@deepseek-ai/*`,vendored 包不再豁免,改名一旦回退就会在打包前失败。
 - `vendor/README.md` 的清单表新增「上游名」列,`gen-third-party-notices` 随之解析六列并把上游名渲进 `THIRD_PARTY_NOTICES.md`;MIT 归属指向 fork 的来源,而不是我们的 scope。
-- `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 删去 `cordis` 与 `@cordisjs/plugin-loader` 两条:改名后这两个名字永远不从 registry 取。`knip.json` 的 `@cordisjs/.+` 忽略模式同理删除,已被 `@deepseek-ai/.+` 覆盖。
+- `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 删去 `cordis` 与 `@cordisjs/plugin-loader` 两条:改名后这两个名字永远不从 registry 取。
 - 上游 sync 照 `vendor/README.md` 的流程走,第 3 步多一项:对拷进来的源码重跑 `pnpm run rescope-vendor --apply`,脚本里的映射与清单表两列名字必须一致。
 - **要回到官方上游包**时反着跑这份映射——`pnpm run rescope-vendor --apply --reverse`——再补回 `minimumReleaseAgeExclude` 两条、放开发布集对 `@deepseek-ai/*` 的断言。改写量约 1300 个文件,用脚本重放而不是手改。
 

+ 2 - 2
.agents/notes/implemented/process/2026-08-12-face-named-client-test-files.i18n.yaml

@@ -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/process/2026-08-12-face-named-client-test-files.md
-2026-08-12-face-named-client-test-files.md: e83ebe4fda25218d96bab546af025b59064ca18f
-2026-08-12-face-named-client-test-files.zh.md: 7062c67f9c98d87f480e119df807955c1a10820a
+2026-08-12-face-named-client-test-files.md: 9bc82ba4297059151f787be53f89736cd5a6935b
+2026-08-12-face-named-client-test-files.zh.md: c6f62b60554146e9006ef43498f752712dd1c23c

+ 1 - 1
.agents/notes/implemented/process/2026-08-12-face-named-client-test-files.md

@@ -56,6 +56,6 @@ The rule costs a suffix on every client test filename and buys a mechanical part
 
 The Host program now sees 11 files under `packages/client` (the four Host-face specs and the carrier's Host-face declarations, resolved through its project reference) instead of the 60 that leaked in while the exclusion was pattern-based but the filenames were not.
 
-vitest discovers every renamed file through `**/*.spec.{ts,tsx}`, so no test configuration changed; the full client suite runs 235 files and 3181 tests. knip's per-workspace `tests/**/*.spec.{ts,tsx}` entry patterns match the new names for the same reason.
+vitest discovers every renamed file through `**/*.spec.{ts,tsx}`, so no test configuration changed; the full client suite runs 235 files and 3181 tests.
 
 An unsuffixed new test is the failure mode this leaves open: it type-checks in the Host program against Client source instead of failing loudly.

+ 1 - 1
.agents/notes/implemented/process/2026-08-12-face-named-client-test-files.zh.md

@@ -56,6 +56,6 @@ Status: implemented
 
 Host 程序现在在 `packages/client` 下看到 11 个文件(4 个 Host 面 spec,以及经工程引用解析到的载体 Host 面声明),而在排除按模式、文件名却不按模式的状态下漏进来的是 60 个。
 
-vitest 经 `**/*.spec.{ts,tsx}` 仍能发现每个改名后的文件,因此测试配置没有变化;完整 client 套件跑 235 个文件、3181 个用例。knip 各 workspace 的 `tests/**/*.spec.{ts,tsx}` entry 模式同理匹配新名字。
+vitest 经 `**/*.spec.{ts,tsx}` 仍能发现每个改名后的文件,因此测试配置没有变化;完整 client 套件跑 235 个文件、3181 个用例。
 
 这条规则留下的失败模式是新增一个不带后缀的测试:它会在 Host 程序里针对 Client 源码通过类型检查,而不是显式报错。

+ 2 - 2
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.i18n.yaml

@@ -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/process/2026-08-18-in-job-partitioned-coverage.md
-2026-08-18-in-job-partitioned-coverage.md: 9b8e6308ec746ee9a31465e57f58457130178b85
-2026-08-18-in-job-partitioned-coverage.zh.md: cf4d50996d9ecd4cb14ee762df11ade4aa63dc31
+2026-08-18-in-job-partitioned-coverage.md: 33824f0aa6f3541df8ca2e0cc8c417b40b5d7933
+2026-08-18-in-job-partitioned-coverage.zh.md: c32738462f7e33d9377d314a9ded5f82cebe9db3

+ 2 - 2
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.md

@@ -14,7 +14,7 @@ The optimization must retain every test and the merged per-file 100% thresholds.
 
 The ordinary `pnpm run test:coverage` command remains one Vitest invocation. Linux coverage CI fixes `DSH_COVERAGE_PARTITIONS=4`; native Windows now also fixes it at 4 to reduce process-creation pressure under high self-hosted concurrency. No elapsed-time trigger changes either count while a run is in progress. The [coverage-exempt heavy suite](2026-07-31-coverage-exempt-heavy-suites.md) remains a separate uninstrumented gate beside the instrumented work.
 
-When partitioning is enabled, `scripts/run-gates.ts` selects `pnpm run test:coverage:partitioned` for the instrumented gate. `scripts/coverage-partitions.ts` starts the configured Vitest children concurrently, each with one worker and one `--shard=<index>/<count>` option. Partition mode suppresses thresholds and coverage reporters in each child, gives every child a separate report directory, and writes one blob report per process.
+When partitioning is enabled, `scripts/run-gates.ts` selects `pnpm run test:coverage:partitioned` for the instrumented gate. `scripts/coverage-partitions.ts` starts the configured Vitest children concurrently, each with one worker. The coordinator collects the instrumented inventory from a `vitest list --filesOnly` run (caller filters narrow it; exempt heavy suites are removed because list does not apply their exclusion), reads recorded per-file durations from a coordinator-persisted gitignored file (restored and saved through the GitHub cache on the windows-coverage job, because checkout removes it and Vitest's own cache never survives CI), and assigns files to partitions by longest-processing-time by way of a min-heap, so the heavy subprocess-bound suites spread across children instead of piling into whichever shard a path hash lands them in. Each partition receives a temporary Vitest config whose include is its file list per project (command-line files exceeded the Windows CreateProcess limit; the mutually exclusive thread-safe and process-bound projects keep only their own files so nothing runs twice), an empty partition is rejected before any child starts, the heaviest partition starts first so its verdict lands earliest (fail-fast), and the duration history is restored and saved through the GitHub cache with per-run keys (cache entries are immutable). Partition mode suppresses thresholds and coverage reporters in each child, gives every child a separate report directory, and writes one blob report per process.
 
 The coordinator waits for every child, validates that the blob directory contains exactly the expected files, and then runs one `vitest --merge-reports ... --coverage` command. Only that merged command applies the repository's per-file statement, branch, function, and line thresholds, so a partition is never judged against an intentionally partial inventory.
 
@@ -28,7 +28,7 @@ A normal failed test still emits a blob through `--coverage.reportOnFailure`, al
 
 ## Verification
 
-`scripts/coverage-partitions.spec.ts` pins argument construction, package-script separator removal, one-worker partitions, the single merged threshold command, failed-test merging, failure diagnostics before complete-blob validation, waiting for sibling partitions after a spawn failure, and link-safe cleanup. `scripts/run-gates.spec.ts` pins opt-in selection, invalid-count rejection, both native Windows coverage gates' complete-build dependency, the complete Windows inventory with its blocking split, and unbuffered streamed output. React fake-timer cases that can move between partitions advance timers inside `act()`; geometry-dependent portal tests stub their element rectangles so a different shard schedule cannot turn deferred updates or jsdom coordinates into coverage-only failures.
+`scripts/coverage-partitions.spec.ts` pins argument construction, package-script separator removal, one-worker partitions, weighted longest-processing-time assignment (including a case that fails when assignment ignores recorded weights), the single merged threshold command, failed-test merging, failure diagnostics before complete-blob validation, waiting for sibling partitions after a spawn failure, and link-safe cleanup. `scripts/run-gates.spec.ts` pins opt-in selection, invalid-count rejection, both native Windows coverage gates' complete-build dependency, the complete Windows inventory with its blocking split, and unbuffered streamed output. React fake-timer cases that can move between partitions advance timers inside `act()`; geometry-dependent portal tests stub their element rectangles so a different shard schedule cannot turn deferred updates or jsdom coordinates into coverage-only failures.
 
 Completed native Windows comparisons measured two partitions near 405 seconds and sixteen partitions at 112.66–122.01 seconds under the earlier gate ordering; those values compare partition latency, not the current peak. The current post-build phase runs four instrumented partition processes beside two exempt workers, for six coverage execution units. Sixteen partitions would raise that phase to eighteen before any still-running production-site work or system overhead. Four partitions keep separate-process isolation and match Linux, at the cost of a longer single-job coverage wall time; the trade-off is accepted to reduce vitest worker startup failures under high self-hosted concurrency. Two Linux samples measured the conservative two-partition configuration at 276.68 and 282.27 seconds; that configuration was stable but halved the ordinary path's four instrumented workers. Four partitions restore that fan-out, for six total coverage execution units on the 16-core hosted runner and at most 36 across the failover VM's six runner instances. These values come from completed runs or fixed capacity bounds; an unfinished run crossing an arbitrary elapsed-time mark is not evidence for increasing concurrency.
 

+ 2 - 2
.agents/notes/implemented/process/2026-08-18-in-job-partitioned-coverage.zh.md

@@ -14,7 +14,7 @@ Status: implemented
 
 普通的 `pnpm run test:coverage` 命令仍只启动一次 Vitest。Linux 覆盖率 CI 将 `DSH_COVERAGE_PARTITIONS` 固定为 4;原生 Windows 现在也固定为 4,以降低自托管高并发下的进程创建压力。运行期间不会由任何耗时触发器改变这两个数量。[覆盖率豁免重型套件](2026-07-31-coverage-exempt-heavy-suites.zh.md)仍作为独立的无插桩门禁与插桩工作并排运行。
 
-启用分区后,`scripts/run-gates.ts` 为插桩门禁选择 `pnpm run test:coverage:partitioned`。`scripts/coverage-partitions.ts` 按配置数量并发启动 Vitest 子进程,每个进程只用 1 个 worker,并各自接收一个 `--shard=<index>/<count>` 选项。分区模式会在各子进程中关闭阈值与覆盖率报告器,为每个子进程分配独立报告目录,并让每个进程写出 1 份 blob 报告。
+启用分区后,`scripts/run-gates.ts` 为插桩门禁选择 `pnpm run test:coverage:partitioned`。`scripts/coverage-partitions.ts` 按配置数量并发启动 Vitest 子进程,每个进程只用 1 个 worker。协调器通过 `vitest list --filesOnly` 收集插桩清单(调用方过滤器会先收窄清单;exempt 重型套件需在此剔除,因为 list 不应用其排除),从协调器持久化的 gitignore 文件读取逐文件耗时(windows-coverage job 通过 GitHub cache 恢复并保存该文件,因为 checkout 会删除它且 Vitest 自身缓存无法在 CI 存活),并借最小堆按最长处理时间把文件分配到各分区,使重量级子进程密集型套件分散到不同子进程,而不是全部落入路径 hash 恰好命中的那一个分片。每个分区获得一个临时 Vitest 配置,其 include 按 project 拆分(命令行传文件会超过 Windows CreateProcess 上限;互斥的 thread-safe 与 process-bound project 只保留各自的文件,避免任何文件跑两次);空分区会在任何子进程启动前被拒绝,最重的分区最先启动使其结论最早落地(fail-fast),耗时历史通过 GitHub cache 以每 run 唯一键恢复与保存(cache 条目不可变)。分区模式会在各子进程中关闭阈值与覆盖率报告器,为每个子进程分配独立报告目录,并让每个进程写出 1 份 blob 报告。
 
 协调器等待全部子进程结束,验证 blob 目录只包含预期文件,然后执行一次 `vitest --merge-reports ... --coverage`。只有这条合并命令应用仓库的逐文件语句、分支、函数与行阈值,因此系统不会拿有意不完整的测试清单单独判定任一分区。
 
@@ -28,7 +28,7 @@ Status: implemented
 
 ## 验证
 
-`scripts/coverage-partitions.spec.ts` 固定了参数构造、包脚本分隔符移除、单 worker 分区、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。`scripts/run-gates.spec.ts` 固定了显式启用、非法数量拒绝、两道原生 Windows 覆盖率门禁对完整构建的依赖、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 `act()` 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。
+`scripts/coverage-partitions.spec.ts` 固定了参数构造、包脚本分隔符移除、单 worker 分区、加权最长处理时间分配(含一个在分配忽略记录权重时必然失败的用例)、唯一一次合并阈值命令、失败测试合并、完整 blob 校验前的失败诊断、spawn 失败后等待兄弟分区,以及链接安全清理。`scripts/run-gates.spec.ts` 固定了显式启用、非法数量拒绝、两道原生 Windows 覆盖率门禁对完整构建的依赖、完整 Windows 清单及其阻断性划分,以及不缓冲的流式输出。可能在分区间移动的 React fake-timer 用例会在 `act()` 内推进计时器;依赖几何位置的 portal 测试会固定元素矩形,使不同分片调度不会把延迟更新或 jsdom 坐标变成只在覆盖率运行中出现的失败。
 
 已完成的原生 Windows 对比中,双分区耗时约 405 秒,16 分区耗时 112.66–122.01 秒;这些数据来自先前的门禁顺序,只用于比较分区延迟,不代表当前峰值。当前的构建后阶段会让 4 个插桩分区进程与 2 个豁免 worker 并行,共形成 6 个覆盖率执行单元。若改为 16 个分区,则在尚未结束的生产网站工作或系统开销计入之前,该阶段就会达到 18 个执行单元。4 个分区保留独立进程隔离并与 Linux 对齐,代价是单 job 覆盖率墙钟更长;这是为了降低自托管高并发下 vitest worker 启动失败而接受的取舍。两个 Linux 样本中,保守的双分区配置耗时 276.68 秒和 282.27 秒;该配置运行稳定,却把普通路径原有的 4 个插桩 worker 减半。4 个分区恢复这份并发,使 16 核托管 runner 上的覆盖率执行单元总数为 6,故障切换虚拟机的 6 个 runner 实例最多合计 36 个执行单元。这些数值来自完整运行或固定容量上限;运行尚未结束时跨过任意耗时刻度,不构成增加并发的证据。
 

+ 6 - 0
.agents/notes/implemented/process/2026-08-19-remove-knip.i18n.yaml

@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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/process/2026-08-19-remove-knip.md
+2026-08-19-remove-knip.md: 5e7e898e05a7e6a6e087b8784f7f49e71cf8fc37
+2026-08-19-remove-knip.zh.md: f6bca8661648efd565dc0bbec86ca2c76738ea85

+ 27 - 0
.agents/notes/implemented/process/2026-08-19-remove-knip.md

@@ -0,0 +1,27 @@
+# Agent Note: Remove Knip from repository gates
+
+Status: implemented
+
+English | [中文](2026-08-19-remove-knip.zh.md)
+
+## Problem
+
+Knip derives unused files, exports, and dependencies from a static source graph. DeepSeek Harness also loads Cordis plugins from package manifests and configuration, emits Typert faces into `lib/`, splits Host and Client programs, and declares dependencies consumed only by generated or runtime-loaded code. The repository therefore needed workspace-specific entry lists and ignored-dependency exceptions to make supported paths pass the scan. Package and test-layout changes had to maintain that second approximation of the executable graph.
+
+The repository already has narrower checks for the failures it treats as release contracts: TypeScript and Oxlint validate source imports, workspace constraints validate manifests, `verify-optional-dependency-imports` validates optional imports, `verify-runtime-closure` validates runtime dependencies, `verify-client-packages` validates Client packaging, and publint validates published packages. The generic unused-code result is advisory, while its exceptions are required maintenance.
+
+## Decision
+
+Knip is not a repository dependency or quality gate. The root manifest has no Knip script or devDependency, the gate graph and `hygiene` command do not invoke it, and the repository carries no Knip configuration. Package guidance and comments describe the runtime or generated-code requirement directly instead of teaching Knip exceptions.
+
+The repository has no repo-wide static check for unused files, exports, or dependencies. Maintainers establish that a removal is safe from call sites, manifests, configuration, generated artifacts, tests, documentation, and Cordis Loader paths.
+
+## Alternatives considered
+
+**Keep Knip and its exception inventory.** This retains one broad advisory signal, but every supported dynamic or generated path needs configuration that restates facts already owned by manifests, build configuration, and package-specific checks. The exception inventory makes ordinary package changes depend on a source graph that does not represent the assembled application.
+
+## Consequences
+
+CI and `hygiene` run one fewer command, and package changes no longer update a parallel entrypoint and dependency-exception inventory. The repository gives up automatic broad unused-code and unused-dependency reports; reviewers and simplification work must prove removals against the real loading paths.
+
+A future unused-code check must understand manifest-driven Cordis loading, generated outputs, and the Host/Client split without a per-workspace ignore inventory. Until then, a dependency with no source import is not dead-code evidence by itself.

+ 27 - 0
.agents/notes/implemented/process/2026-08-19-remove-knip.zh.md

@@ -0,0 +1,27 @@
+# Agent Note: 从仓库门禁中移除 Knip
+
+Status: implemented
+
+[English](2026-08-19-remove-knip.md) | 中文
+
+## 问题
+
+Knip 从静态源码图推导未使用文件、export 和依赖。DeepSeek Harness 还会从包 manifest(元数据清单)和配置中装载 Cordis 插件、向 `lib/` 生成 Typert 契约面、拆分 Host 与 Client 程序,并声明仅由生成代码或运行时装载代码消费的依赖。因此,仓库必须维护 workspace 专用入口列表和忽略依赖例外,才能让受支持路径通过扫描。包与测试布局变化还必须维护这份对可执行图的第二重近似描述。
+
+仓库已经为视作发布约定的故障提供了更窄的检查:TypeScript 与 Oxlint 验证源码 import,workspace 约束验证 manifest,`verify-optional-dependency-imports` 验证可选 import,`verify-runtime-closure` 验证运行时依赖,`verify-client-packages` 验证 Client 打包,publint 验证发布包。通用未使用代码结果只是建议,而其例外却是必需维护项。
+
+## 决策
+
+Knip 不再是仓库依赖或质量门禁。根 manifest 不含 Knip 脚本或 devDependency,门禁图和 `hygiene` 命令不调用它,仓库也不携带 Knip 配置。包指南和注释直接描述运行时或生成代码要求,不再讲解 Knip 例外。
+
+仓库没有检查全仓未使用文件、export 或依赖的静态门禁。维护者需要从调用点、manifest、配置、生成产物、测试、文档和 Cordis Loader 路径证明一项删除是安全的。
+
+## 考虑过的替代方案
+
+**保留 Knip 及其例外清单。** 这会保留一项宽泛的建议信号,但每条受支持的动态或生成路径都需要配置,而这些配置会重述 manifest、构建配置和包专用检查已经拥有的事实。这份例外清单使普通包变更依赖一个无法表示组装后应用的源码图。
+
+## 后果
+
+CI 和 `hygiene` 少运行一个命令,包变更也不再更新一份并行的入口与依赖例外清单。仓库放弃自动生成宽泛的未使用代码与未使用依赖报告;评审与简化工作必须根据真实装载路径证明删除安全。
+
+未来的未使用代码检查必须理解 manifest 驱动的 Cordis 装载、生成输出与 Host/Client 拆分,且不需要逐 workspace 忽略清单。在此之前,缺少源码 import 的依赖本身不能证明该依赖是死代码。

+ 2 - 2
.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml

@@ -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/simplification/2026-07-22-plan-specific-collaboration-state.md
-2026-07-22-plan-specific-collaboration-state.md: 1387e69e628c5fb33b61a06214ffd67669668a13
-2026-07-22-plan-specific-collaboration-state.zh.md: 47d36eb97a85d483e40afb5fb9c44f1bf2c92d5e
+2026-07-22-plan-specific-collaboration-state.md: b477623872d8f5e1ac21836c9d43dee23a83edc6
+2026-07-22-plan-specific-collaboration-state.zh.md: 4af06f2212b6db6d4735670128299df4b9e8a3fb

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md

@@ -14,7 +14,7 @@ Plan mode also needs a durable stance, a reviewable plan artifact, an explicit h
 
 ## Decision
 
-Plan mode owns a plan-specific product package: `@deepseek-ai/dsh-plan-mode` at `packages/plan/plan-mode/`. The durable fact is `plan/mode: { active: boolean }`, folded by `foldPlanMode(events)` with `false` as the empty-log value. `ctx.planMode.get(agent)` returns `{ active, pending? }`, and `set(agent, active)` records the boundary-applied selection. The pre-step, retry, append-failure, and disposal fences preserve the same state-transition ownership.
+Plan mode owns a plan-specific product package: `@deepseek-ai/dsh-plan-mode` at `packages/plan/plan-mode/`. The durable fact is `plan/mode: { active: boolean }`, folded by the package's `plan` projection unit — `planProjectionDefinition.apply` over committed events, with the empty log folding to inactive (`active: false`) — and every projection unit is now persisted uniformly, the `persist` option having been removed. Host logic reads the fold through `ctx.sessionProjections.stateOf(session, 'plan')`; the client view is `{ active, pending }`. `ctx.planMode.get(agent)` returns `{ active, pending? }`, and `set(agent, active)` records the boundary-applied selection. The pre-step, retry, append-failure, and disposal fences preserve the same state-transition ownership.
 
 Configuration is exactly `{ section: string }`. The package registers the fixed `plan:policy` section, `/plan [message]`, the exact `/plan off` direct-exit form, and `exit_plan_mode` itself. Bare `/plan` selects active; another non-empty argument selects it first and then sends the trimmed text through `agent.steer()`, making the text an ordinary logged user message in the affected step. `/plan off` selects inactive without model input and can cancel an entry that is still pending at the boundary. The exit tool remains registered while plan mode is inactive so the request tool catalog stays stable.
 

+ 1 - 1
.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md

@@ -14,7 +14,7 @@ Plan mode 还需要持久协作状态、可评审的计划产物、显式人工
 
 ## 决策
 
-Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` 的 `@deepseek-ai/dsh-plan-mode`。持久化事实为 `plan/mode: { active: boolean }`,由 `foldPlanMode(events)` 折叠,空日志值为 `false`。`ctx.planMode.get(agent)` 返回 `{ active, pending? }`,`set(agent, active)` 则记录在边界生效的选择。pre-step、重试、追加失败和 dispose(资源释放)栅栏保留相同的状态转换归属。
+Plan mode 拥有一个 plan 专用产品包:位于 `packages/plan/plan-mode/` 的 `@deepseek-ai/dsh-plan-mode`。持久化事实为 `plan/mode: { active: boolean }`,由本包的 `plan` 投影单元折叠——`planProjectionDefinition.apply` 作用于已提交事件,空日志折叠为未激活(`active: false`)——所有投影单元现在一律持久化,`persist` 选项已删除。host 逻辑通过 `ctx.sessionProjections.stateOf(session, 'plan')` 读取折叠结果,客户端视图为 `{ active, pending }`。`ctx.planMode.get(agent)` 返回 `{ active, pending? }`,`set(agent, active)` 则记录在边界生效的选择。pre-step、重试、追加失败和 dispose(资源释放)栅栏保留相同的状态转换归属。
 
 配置严格为 `{ section: string }`。该包自行注册固定的 `plan:policy` 段、`/plan [message]`、精确匹配的 `/plan off` 主动退出形式,以及 `exit_plan_mode`。不带参数的 `/plan` 选择激活;其他非空参数则先选择激活,再通过 `agent.steer()` 发送去除首尾空白后的文本,使该文本在受影响的步骤中成为一条记录到日志的普通用户消息。`/plan off` 选择未激活,不产生模型输入,并可取消仍待在边界生效的进入选择。即使 plan mode 未激活,退出工具仍保持注册,以确保请求工具目录稳定。
 

+ 2 - 2
.agents/notes/proposed/process/2026-06-20-discover-package-inventory.i18n.yaml

@@ -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/proposed/process/2026-06-20-discover-package-inventory.md
-2026-06-20-discover-package-inventory.md: 90ad595312431b942f3aac562e4ce8843fb6cc83
-2026-06-20-discover-package-inventory.zh.md: 4c5312f17ddeab1d6457a1a904f921f44e7d0b9d
+2026-06-20-discover-package-inventory.md: e1448b696d8c11d9e437ac7e7df18646f43392ef
+2026-06-20-discover-package-inventory.zh.md: dc8172652e2483271c4c68264ea9f46640a5ce22

+ 1 - 4
.agents/notes/proposed/process/2026-06-20-discover-package-inventory.md

@@ -6,7 +6,7 @@ English | [中文](2026-06-20-discover-package-inventory.zh.md)
 
 ## Problem
 
-Package and gate inventories are repeated across TypeScript project references, package docs, CI prose, and Knip overrides. Most restate package layout, manifest data, or aggregate command contents. Each new package therefore creates avoidable synchronization points.
+Package and gate inventories are repeated across TypeScript project references, package docs, and CI prose. Most restate package layout, manifest data, or aggregate command contents. Each new package therefore creates avoidable synchronization points.
 
 The [package hierarchy](../../archived/architecture/2026-06-20-package-hierarchy.md) already removed several of these by hand: `scripts/publint-all.ts` now derives its list from the `packages/<group>/<pkg>` layout, and the two `tsconfig` `paths` maps collapsed to one `@deepseek-ai/dsh-*` wildcard — since reverted to one explicit alias per package, generated and gated, because resolving a wildcard's candidates in order dominated source-launch boot ([explicit workspace path aliases](../../implemented/process/2026-08-27-explicit-workspace-path-aliases.md)). What remains is the inventory that cannot be globbed away — chiefly the aggregate configs' (`tsconfig.host.json`, `tsconfig.client.json`) project `references`, which TypeScript requires as explicit arrays (no wildcard form).
 
@@ -18,15 +18,12 @@ Make the remaining package/gate inventories discoverable. A single canonical sou
 
 The hierarchy does not need to encode every fact about a package, but it should encode the broad maintenance policy: core/product packages, integrations, capability seams, and support/test/example packages should not all require a hand-maintained exception list before scripts can tell them apart.
 
-One cataloged item needs no generator at all: folding the e2e entry glob into knip's default stanza deletes the per-package restatements outright.
-
 ## Acceptance criteria
 
 - Aggregate-config project `references` are generated from the hierarchy (a generator emits them; a `--check` gate fails when the committed copy is stale), rather than hand-maintained.
 - Adding a package does not require editing a static package list for any gate.
 - Docs describe the source of truth rather than repeating generated inventories.
 - CI invokes the aggregate commands and lets those commands own their sub-gate lists.
-- `knip.json` carries a per-package override only where it encodes real information (an extra entry file, an ignored dependency), never a restatement of the default stanza.
 
 ## Risks
 

+ 1 - 4
.agents/notes/proposed/process/2026-06-20-discover-package-inventory.zh.md

@@ -6,7 +6,7 @@ Status: proposed
 
 ## 问题
 
-包与门禁清单在 TypeScript project references、包文档、CI 描述和 Knip 覆盖项中反复出现。大多数只是重述包布局、manifest(元数据清单)数据或聚合命令内容。因此每新增一个包都会产生本可避免的同步点。
+包与门禁清单在 TypeScript project references、包文档和 CI 描述中反复出现。大多数只是重述包布局、manifest(元数据清单)数据或聚合命令内容。因此每新增一个包都会产生本可避免的同步点。
 
 [包层级结构](../../archived/architecture/2026-06-20-package-hierarchy.md)已经手动消除了其中若干:`scripts/publint-all.ts` 现在从 `packages/<group>/<pkg>` 布局推导列表,两份 `tsconfig` 的 `paths` 映射也合并为一个 `@deepseek-ai/dsh-*` 通配符——该合并此后已被回退为「每包一条、由生成器托管并有门禁把关」的显式别名,因为按顺序试候选的解析开销主导了源码启动时间([显式 workspace 路径别名](../../implemented/process/2026-08-27-explicit-workspace-path-aliases.zh.md))。剩下的是无法用 glob 消除的清单,主要是聚合配置(`tsconfig.host.json`、`tsconfig.client.json`)中的项目引用(`references`)——TypeScript 要求它们是显式数组(没有通配符形式)。
 
@@ -18,15 +18,12 @@ Status: proposed
 
 层级结构不需要编码关于包的所有事实,但应当编码宽泛的维护策略:core/product 包、集成包、能力 seam 包与 support/test/example 包不应在脚本能区分它们之前先要求一份手工维护的例外列表。
 
-有一项已编目的内容根本不需要生成器:将 e2e 入口 glob 折入 Knip 的默认配置段,即可直接删除逐包的重复声明。
-
 ## 验收标准
 
 - 聚合配置的项目引用(`references`)由层级结构生成(生成器输出它们;`--check` 门禁在提交副本陈旧时报错),而非手工维护。
 - 新增一个包时,不需要为任何门禁编辑静态包列表。
 - 文档描述真源,而非重复生成的清单。
 - CI 调用聚合命令,由这些命令自行管理其子门禁列表。
-- `knip.json` 仅在编码真实信息(额外入口文件、被忽略的依赖)时才携带逐包覆盖项,绝不重述默认配置段。
 
 ## 风险
 

+ 1 - 1
.agents/skills/dsh-find-simplifications/SKILL.md

@@ -76,7 +76,7 @@ For every symbol or behavior, classify consumers before writing:
 - Non-production corpus: tests, README/docs, Agent Notes, snapshots, generated expected outputs, and comments.
 - Ambiguous corpus: examples and scripts that may be product smoke paths. Inspect usage before classifying.
 
-Use `rg` first. Good searches include the exact symbol, event name, package name, config key, method name with both `.name(` and `name(`, and any wire strings. Then read the call sites. `knip` can help, but it is not a substitute for understanding public interfaces, dynamic event names, tests, docs, and Cordis loader paths.
+Use `rg` first. Good searches include the exact symbol, event name, package name, config key, method name with both `.name(` and `name(`, and any wire strings. Then read the call sites, public interfaces, dynamic event names, tests, docs, and Cordis loader paths.
 
 Reject or downgrade a candidate when:
 

+ 22 - 0
.github/workflows/ci.yml

@@ -452,6 +452,19 @@ jobs:
       - uses: actions/checkout@v6
         with:
           persist-credentials: false
+      # Checkout removes the gitignored duration history, so restore it from
+      # the GitHub cache before coverage and save the updated one afterwards.
+      # The coordinator then weights partitions by measured durations on the
+      # second run instead of degrading to a file-count split. GitHub cache
+      # entries are immutable, so the save key is unique per run and the
+      # restore matches the newest entry through the stable prefix.
+      - name: Restore coverage duration history
+        uses: actions/cache/restore@v4
+        with:
+          path: .coverage-times.json
+          key: coverage-times-${{ github.run_id }}
+          restore-keys: |
+            coverage-times-
       - name: Enable Developer Mode (symlink support)
         shell: pwsh
         run: >-
@@ -472,6 +485,15 @@ jobs:
       - name: Run Windows coverage
         shell: pwsh
         run: pnpm run check:ci:coverage
+      - name: Save coverage duration history
+        # Coverage flakes must not prevent the cache from building; the
+        # measured durations remain useful even when a partition failed. The
+        # per-run key keeps every save a fresh immutable cache entry.
+        if: always()
+        uses: actions/cache/save@v4
+        with:
+          path: .coverage-times.json
+          key: coverage-times-${{ github.run_id }}
 
   windows-native-tests:
     if: github.event_name == 'pull_request'

+ 1 - 0
.gitignore

@@ -11,6 +11,7 @@ examples/*/*.jsonl
 .sessions/
 examples/*/.sessions/
 coverage/
+.coverage-times.json
 .doc-typecheck-*/
 .node-next-types-*/
 .oxlint-contract-*/

+ 1 - 1
AGENTS.md

@@ -74,7 +74,7 @@ pnpm run typecheck
 pnpm run lint
 pnpm run duplication    # cross-file TypeScript clone detection
 pnpm run build          # tsc emits lib/types, tsdown bundles runtime
-pnpm run hygiene        # knip + publint + workspace constraints + NodeNext consumer check
+pnpm run hygiene        # publint + workspace/package/dependency checks + NodeNext consumer check
 pnpm run check:windows-wine  # ONLY when diagnosing a known Windows failure (needs wine); CI owns this signal
 pnpm run doc-sync       # all documentation gates; leaf list in scripts/run-gates.ts
 pnpm run test:docs      # quick documentation checks (no build; doc-quick aggregate)

+ 0 - 1
THIRD_PARTY_NOTICES.md

@@ -174,7 +174,6 @@ External packages **directly declared** only by repository tooling, test infrast
 | [`istanbul-lib-report`](https://github.com/istanbuljs/istanbuljs) | BSD-3-Clause |
 | [`jscpd`](https://github.com/kucherenko/jscpd) | MIT |
 | [`jsdom`](https://github.com/jsdom/jsdom) | MIT |
-| [`knip`](https://github.com/webpro-nl/knip) | ISC |
 | [`lefthook`](https://github.com/evilmartians/lefthook) | MIT |
 | [`lightningcss`](https://github.com/parcel-bundler/lightningcss) | MPL-2.0 |
 | [`mermaid`](https://github.com/mermaid-js/mermaid) | MIT |

+ 1 - 0
apps/cli/tests/built-bin.e2e.ts

@@ -950,6 +950,7 @@ describe.skipIf(!existsSync(dshBin))('dsh BUILT bin (node lib/bin.js, no tsx)',
         ['plugin-package-inventory-deepseek', '@deepseek-ai/dsh-plugin-package-inventory-deepseek'],
         ['llm-deepseek', '@deepseek-ai/dsh-llm-deepseek'],
         ['sandbox', '@deepseek-ai/dsh-sandbox-local'],
+        ['session-projection', '@deepseek-ai/dsh-session-projection'],
         ['sandbox-policy', '@deepseek-ai/dsh-sandbox-policy'],
         ['subprocess', '@deepseek-ai/dsh-subprocess-local'],
         ['pty', '@deepseek-ai/dsh-terminal'],

+ 1 - 2
apps/cli/tests/fixtures/dsh-badge/snapshot.ts

@@ -1,5 +1,4 @@
 import { fileURLToPath } from 'node:url'
-import { Context } from '@deepseek-ai/cordis'
 import { agentEvents, Inbox, type Agent } from '@deepseek-ai/dsh-agent'
 import { ToolCallId } from '@deepseek-ai/dsh-llm'
 import { boot, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
@@ -20,7 +19,7 @@ try {
   const agentId = SessionId('dsh-badge-snapshot')
   const session = ctx.sessions.create(agentId, { meta: { cwd: process.cwd() } })
   const agent: Agent = {
-    ctx: new Context(),
+    ctx,
     id: agentId,
     options: {},
     session,

+ 2 - 0
apps/cli/tests/profiles/headless/tests/harness.ts

@@ -2,6 +2,7 @@ import { Context } from '@deepseek-ai/cordis'
 import type { SessionEvent } from '@deepseek-ai/dsh-session'
 import type { Agent } from '@deepseek-ai/dsh-agent'
 import AgentLoop from '@deepseek-ai/dsh-agent-loop'
+import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
 import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
 import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
 import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
@@ -55,6 +56,7 @@ export interface CodingHarnessOptions {
 
 export async function codingHarness(workdir: string, options: CodingHarnessOptions = {}): Promise<Context> {
   const ctx = new Context()
+  await ctx.plugin(SessionProjectionRegistry)
   await mountAgentLoopTestDependencies(ctx, {
     systemPrompt: { persona: options.persona ?? '' },
   })

+ 3 - 0
apps/cli/tests/profiles/headless/tests/ptc.e2e.ts

@@ -12,6 +12,7 @@ import type { ToolExecutionResult } from '@deepseek-ai/dsh-tools'
 import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
 
 import AgentLoop from '@deepseek-ai/dsh-agent-loop'
+import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
 import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
 import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
 import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
@@ -53,6 +54,7 @@ async function ptcModeHarness(cwd: string): Promise<Context> {
   const harness = new Context()
   await harness.plugin(LlmRuntime)
   await harness.plugin(SessionStore)
+  await harness.plugin(SessionProjectionRegistry)
   await harness.plugin(SystemPrompt, { persona: PERSONA })
   await harness.plugin(ToolRuntime, { mode: 'ptc' })
   await harness.plugin(AgentRegistry)
@@ -70,6 +72,7 @@ async function workspacePtcModeHarness(): Promise<Context> {
   const harness = new Context()
   await harness.plugin(LlmRuntime)
   await harness.plugin(SessionStore)
+  await harness.plugin(SessionProjectionRegistry)
   await harness.plugin(SystemPrompt, { persona: PERSONA })
   await harness.plugin(ToolRuntime, { mode: 'ptc' })
   await harness.plugin(AgentRegistry)

+ 2 - 2
docs/architecture.i18n.yaml

@@ -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 docs/architecture.md
-architecture.md: 5717bf62b0967ff860dee9db5265d8dd56242a1d
-architecture.zh.md: 279d8c4cc040bc18f7adfd1ae1b782447c3333d6
+architecture.md: 60b678232dc1ae33c2e6512645c4e2e70477e0e7
+architecture.zh.md: 14914f456f1c504f9b06a87cb1b1ef9bed0b83fa

+ 2 - 0
docs/architecture.md

@@ -106,6 +106,8 @@ The session log is the source of the context the model sees. `deriveMessages()`
 
 **Model-visible means logged.** Anything that reaches a model request must be reconstructable from the log, and a runtime invariant asserts it. This is why a new model-visible input requires a new session event: extend `SessionEventMap` and render from the log.
 
+**Projection seam.** `dsh-session-projection` owns `ctx.sessionProjections`: registered units fold committed events incrementally, host consumers read one typed state with `stateOf()`, and carriers batch cropped client views with `snapshot()`. A host reader either requires this service during activation or fails explicitly when the registry or required key is absent. Contributors may retain `ctx.inject(['sessionProjections'], ...)` registration without silently defaulting a missing host value. The agent loop registers shared `turnBoundary` state for its readers ([decision](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.md)).
+
 ## Capability seams
 
 A **seam** is a swappable capability with three roles: a **Service Definition** declaring the interface, a **Service Provider** implementing it, and a **Consumer** using it, commonly a model-facing tool. A package may combine roles, but one role alone is not a seam; adding a capability means designing all three ([capability graph](capability-seams.md)).

+ 2 - 0
docs/architecture.zh.md

@@ -110,6 +110,8 @@ turn/end
 
 **模型可见即已记录。** 抵达模型请求的一切都必须能从日志重建,并由一项运行时不变量断言这一点。因此,新增一项模型可见输入就需要新增一个会话事件:扩展 `SessionEventMap` 并从日志渲染。
 
+**投影 seam。** `dsh-session-projection` 提供 `ctx.sessionProjections`:已注册单元增量折叠已提交事件,host 消费方通过 `stateOf()` 读取单个类型化状态,载体通过 `snapshot()` 批量取得裁剪后的客户端视图。host 读取方要么在激活时要求该服务,要么在注册表或必需 key 缺席时明确失败。贡献方可以保留 `ctx.inject(['sessionProjections'], ...)` 注册,但不能为缺失的 host 值静默提供默认值。agent loop 为读取方注册共享的 `turnBoundary` 状态([决策](../.agents/notes/implemented/architecture/2026-08-19-session-projection-mandatory-seam.zh.md))。
+
 ## 能力 seam
 
 一个 **seam** 是一项可替换能力,包含三种角色:声明接口的 **Service Definition**、实现它的 **Service Provider**,以及使用它的 **Consumer**(通常是面向模型的工具)。一个包可以合并承担多个角色,但单一角色本身不是 seam;添加一项能力意味着把三者一并设计([能力图](capability-seams.zh.md))。

+ 2 - 2
docs/config-catalog.i18n.yaml

@@ -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 docs/config-catalog.md
-config-catalog.md: 7e4e4b626161fb8468da48d07dabe51b626c94d0
-config-catalog.zh.md: 2e48ea06cb4179db78b946e55f289dd1d7bc71e1
+config-catalog.md: 2ed8dd593e69b00d659ac71e96e856456a5e9185
+config-catalog.zh.md: dd22bea8e2f47179d9458cfdba1f74726fb78ae2

+ 36 - 30
docs/config-catalog.md

@@ -53,6 +53,8 @@ Source: [`packages/core/agent-default-model/src/index.ts:41`](../packages/core/a
 
 ## `@deepseek-ai/dsh-agent-instructions`
 
+Requires: `sessionProjections`
+
 ```ts config-catalog
 /** User-facing workspace instruction loader configuration. */
 export interface Config {
@@ -83,7 +85,7 @@ Source: [`packages/context/agent-instructions/src/config.ts:18`](../packages/con
 
 ## `@deepseek-ai/dsh-agent-loop`
 
-Requires: `agents` · `sessions` · `llm` · `tools` · `systemPrompt`
+Requires: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Agent-loop plugin configuration. */
@@ -109,13 +111,13 @@ export interface Config {
 
 Depends on: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/core.md)
 
-Source: [`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts)
+Source: [`packages/core/agent-loop/src/index.ts:310`](../packages/core/agent-loop/src/index.ts)
 
 <a id="deepseek-aidsh-agent-presets"></a>
 
 ## `@deepseek-ai/dsh-agent-presets`
 
-Requires: `loader`
+Requires: `loader` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: which preset is the default, and where presets live. */
@@ -248,7 +250,7 @@ export interface GoalConfig {
 
 Depends on: [`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`JobsConfig`](#deepseek-aidsh-jobs-local) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillFileSystem`](../packages/skill/skill-filesystem/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/shell/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`toolJobs`](../packages/jobs/tool-jobs/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`workspaceContext`](../packages/context/agent-instructions/src/index.ts)
 
-Source: [`packages/examples/agent-spine-demo/src/index.ts:93`](../packages/examples/agent-spine-demo/src/index.ts)
+Source: [`packages/examples/agent-spine-demo/src/index.ts:94`](../packages/examples/agent-spine-demo/src/index.ts)
 
 <a id="deepseek-aidsh-agent-tool-presentation"></a>
 
@@ -608,7 +610,7 @@ Source: [`packages/e2b/e2b/src/index.ts:43`](../packages/e2b/e2b/src/index.ts)
 
 ## `@deepseek-ai/dsh-experimental-agent-team`
 
-Requires: `agents` · `sessions` · `sessionPersistence` · `subagents`
+Requires: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents`
 
 ```ts config-catalog
 /** Team-service deployment limits. */
@@ -777,7 +779,7 @@ Source: [`packages/fs/fs-sandbox/src/index.ts:45`](../packages/fs/fs-sandbox/src
 
 ## `@deepseek-ai/dsh-goal`
 
-Requires: `agents`
+Requires: `agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment defaults for goal creation. */
@@ -787,7 +789,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
+Source: [`packages/goal/goal/src/index.ts:171`](../packages/goal/goal/src/index.ts)
 
 <a id="deepseek-aidsh-headless"></a>
 
@@ -809,7 +811,7 @@ Source: [`packages/bundle/headless/src/index.ts:32`](../packages/bundle/headless
 
 ## `@deepseek-ai/dsh-hooks-claude-code`
 
-Requires: `shell`
+Requires: `shell` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: where the CC hook config lives + substitution roots. */
@@ -841,13 +843,13 @@ export interface Config {
 }
 ```
 
-Source: [`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts)
+Source: [`packages/hooks/hooks-claude-code/src/index.ts:46`](../packages/hooks/hooks-claude-code/src/index.ts)
 
 <a id="deepseek-aidsh-hooks-codex"></a>
 
 ## `@deepseek-ai/dsh-hooks-codex`
 
-Requires: `shell`
+Requires: `shell` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: where the Codex hooks.json lives + the model name for payloads. */
@@ -868,7 +870,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts)
+Source: [`packages/hooks/hooks-codex/src/index.ts:45`](../packages/hooks/hooks-codex/src/index.ts)
 
 <a id="deepseek-aidsh-host-directory-picker-browse"></a>
 
@@ -1385,14 +1387,14 @@ Source: [`packages/test-support/llm-replay/src/index.ts:918`](../packages/test-s
 
 ## `@deepseek-ai/dsh-llm-retry`
 
-Requires: `agents`
+Requires: `agents` · `sessionProjections`
 
 ```ts config-catalog
 /** This policy executor has no config; providers own `retryPolicy`. */
 export type Config = Readonly<Record<string, never>>
 ```
 
-Source: [`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts)
+Source: [`packages/llm/llm-retry/src/index.ts:25`](../packages/llm/llm-retry/src/index.ts)
 
 <a id="deepseek-aidsh-lsp-stdio"></a>
 
@@ -1529,7 +1531,7 @@ Source: [`packages/feedback/message-feedback/src/index.ts:49`](../packages/feedb
 
 ## `@deepseek-ai/dsh-permission-presets`
 
-Requires: `shell` · `approval` · `sessions`
+Requires: `shell` · `approval` · `sessions` · `sessionProjections`
 
 ```ts config-catalog
 /** The {@link PermissionPresetService} config: preset table and composition default. */
@@ -1562,7 +1564,7 @@ export interface PresetSpec {
 
 Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md)
 
-Source: [`packages/interaction/permission-presets/src/index.ts:156`](../packages/interaction/permission-presets/src/index.ts)
+Source: [`packages/interaction/permission-presets/src/index.ts:143`](../packages/interaction/permission-presets/src/index.ts)
 
 <a id="deepseek-aidsh-persona"></a>
 
@@ -1592,7 +1594,7 @@ Source: [`packages/preset/persona/src/index.ts:34`](../packages/preset/persona/s
 
 ## `@deepseek-ai/dsh-plan-mode`
 
-Requires: `tools` · `systemPrompt`
+Requires: `tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment-owned plan guidance. */
@@ -1602,7 +1604,7 @@ export interface PlanModeConfig {
 }
 ```
 
-Source: [`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts)
+Source: [`packages/plan/plan-mode/src/index.ts:64`](../packages/plan/plan-mode/src/index.ts)
 
 <a id="deepseek-aidsh-plugin-package-inventory-deepseek"></a>
 
@@ -1745,6 +1747,8 @@ Source: [`packages/sandbox/sandbox-local/src/index.ts:44`](../packages/sandbox/s
 
 ## `@deepseek-ai/dsh-sandbox-policy`
 
+Requires: `sessionProjections`
+
 ```ts config-catalog
 /**
  * Plugin config: the deployment's sandbox default. All optional — `Config`
@@ -1766,7 +1770,7 @@ export interface Config {
 
 Depends on: [`SandboxMode`](subsystems/sandbox.md)
 
-Source: [`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts)
+Source: [`packages/sandbox/sandbox-policy/src/index.ts:70`](../packages/sandbox/sandbox-policy/src/index.ts)
 
 <a id="deepseek-aidsh-sdk-app"></a>
 
@@ -2049,7 +2053,7 @@ Source: [`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/
 
 ## `@deepseek-ai/dsh-session-title`
 
-Requires: `sessions`
+Requires: `sessions` · `sessionProjections`
 
 ```ts config-catalog
 /** Required deterministic fallback and accepted-title limits. */
@@ -2063,7 +2067,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts)
+Source: [`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
 
 <a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
 
@@ -2570,7 +2574,7 @@ Source: [`packages/core/system-prompt/src/index.ts:237`](../packages/core/system
 
 ## `@deepseek-ai/dsh-terminal-bash`
 
-Requires: `terminals` · `sandboxPolicy` · `subprocess`
+Requires: `terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess`
 
 ```ts config-catalog
 /** Public plugin configuration. */
@@ -2620,7 +2624,7 @@ Source: [`packages/terminal/terminal-bash/src/config.ts:10`](../packages/termina
 
 ## `@deepseek-ai/dsh-time-context`
 
-Requires: `agents`
+Requires: `agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Request-preparation clock formatting and append scheduling. Invalid values fail plugin load. */
@@ -2632,13 +2636,13 @@ export interface Config {
 }
 ```
 
-Source: [`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts)
+Source: [`packages/context/time-context/src/index.ts:48`](../packages/context/time-context/src/index.ts)
 
 <a id="deepseek-aidsh-tmux-context"></a>
 
 ## `@deepseek-ai/dsh-tmux-context`
 
-Requires: `agents`
+Requires: `agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Per-turn tmux-location scheduling. Invalid values fail plugin load. */
@@ -2648,12 +2652,14 @@ export interface Config {
 }
 ```
 
-Source: [`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts)
+Source: [`packages/context/tmux-context/src/index.ts:36`](../packages/context/tmux-context/src/index.ts)
 
 <a id="deepseek-aidsh-token-meter"></a>
 
 ## `@deepseek-ai/dsh-token-meter`
 
+Requires: `sessionProjections`
+
 ```ts config-catalog
 /** Token-meter plugin configuration; the fixed estimator has no settings. */
 export type TokenMeterConfig = Record<string, never>
@@ -2760,7 +2766,7 @@ Source: [`packages/fs/tool-fs-search/src/index.ts:73`](../packages/fs/tool-fs-se
 
 ## `@deepseek-ai/dsh-tool-goal`
 
-Requires: `agents` · `goals` · `tools` · `systemPrompt`
+Requires: `agents` · `goals` · `tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Model policy and hard lower bounds for goal-state updates. */
@@ -2890,7 +2896,7 @@ Source: [`packages/workflow/tool-ralph/src/index.ts:22`](../packages/workflow/to
 
 ## `@deepseek-ai/dsh-tool-session-query`
 
-Requires: `tools` · `systemPrompt` · `sessionQuery`
+Requires: `tools` · `systemPrompt` · `sessionQuery` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment-owned search count and timeout bounds. */
@@ -2942,7 +2948,7 @@ Source: [`packages/fs/tool-str-replace-editor/src/index.ts:505`](../packages/fs/
 
 ## `@deepseek-ai/dsh-tool-subagent`
 
-Requires: `tools` · `subagents` · `systemPrompt`
+Requires: `tools` · `subagents` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Config: which registered provider this tool delegates to, plus child defaults. */
@@ -3006,7 +3012,7 @@ export interface Config {
 
 Depends on: [`AgentOptions`](subsystems/core.md)
 
-Source: [`packages/subagent/tool-subagent/src/index.ts:49`](../packages/subagent/tool-subagent/src/index.ts)
+Source: [`packages/subagent/tool-subagent/src/index.ts:50`](../packages/subagent/tool-subagent/src/index.ts)
 
 <a id="deepseek-aidsh-tool-subagent-report"></a>
 
@@ -3052,7 +3058,7 @@ Source: [`packages/terminal/tool-terminal/src/index.ts:36`](../packages/terminal
 
 ## `@deepseek-ai/dsh-tool-todo`
 
-Requires: `tools`
+Requires: `tools` · `sessionProjections`
 
 ```ts config-catalog
 /** Model-facing todo tool configuration. */

+ 37 - 31
docs/config-catalog.zh.md

@@ -55,6 +55,8 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-agent-instructions`
 
+需要:`sessionProjections`
+
 ```ts config-catalog
 /** User-facing workspace instruction loader configuration. */
 export interface Config {
@@ -85,7 +87,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-agent-loop`
 
-需要:`agents` · `sessions` · `llm` · `tools` · `systemPrompt`
+需要:`agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Agent-loop plugin configuration. */
@@ -111,13 +113,13 @@ export interface Config {
 
 依赖:[`AgentOptions`](subsystems/core.zh.md) · [`SessionId`](subsystems/core.zh.md)
 
-来源:[`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts)
+来源:[`packages/core/agent-loop/src/index.ts:310`](../packages/core/agent-loop/src/index.ts)
 
 <a id="deepseek-aidsh-agent-presets"></a>
 
 ## `@deepseek-ai/dsh-agent-presets`
 
-需要:`loader`
+需要:`loader` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: which preset is the default, and where presets live. */
@@ -250,7 +252,7 @@ export interface GoalConfig {
 
 依赖:[`AgentLoopConfig`](#deepseek-aidsh-agent-loop) · [`GoalDomainConfig`](#deepseek-aidsh-goal) · [`InvariantConfig`](#deepseek-aidsh-invariants) · [`JobsConfig`](#deepseek-aidsh-jobs-local) · [`SessionTitleConfig`](#deepseek-aidsh-session-title) · [`SkillFileSystem`](../packages/skill/skill-filesystem/src/index.ts) · [`SkillRegistryConfig`](#deepseek-aidsh-skill) · [`SystemPromptConfig`](#deepseek-aidsh-system-prompt) · [`toolBash`](../packages/shell/tool-bash/src/index.ts) · [`toolGoal`](../packages/goal/tool-goal/src/index.ts) · [`toolJobs`](../packages/jobs/tool-jobs/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) · [`toolSkill`](../packages/skill/tool-skill/src/index.ts) · [`workspaceContext`](../packages/context/agent-instructions/src/index.ts)
 
-来源:[`packages/examples/agent-spine-demo/src/index.ts:93`](../packages/examples/agent-spine-demo/src/index.ts)
+来源:[`packages/examples/agent-spine-demo/src/index.ts:94`](../packages/examples/agent-spine-demo/src/index.ts)
 
 <a id="deepseek-aidsh-agent-tool-presentation"></a>
 
@@ -610,7 +612,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-experimental-agent-team`
 
-需要:`agents` · `sessions` · `sessionPersistence` · `subagents`
+需要:`agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents`
 
 ```ts config-catalog
 /** Team-service deployment limits. */
@@ -720,7 +722,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-file-reference-local`
 
-需要:`agents`
+需要:`agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Local file-reference discovery configuration. */
@@ -779,7 +781,7 @@ export type Config = LocalConfig
 
 ## `@deepseek-ai/dsh-goal`
 
-需要:`agents`
+需要:`agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment defaults for goal creation. */
@@ -789,7 +791,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
+来源:[`packages/goal/goal/src/index.ts:171`](../packages/goal/goal/src/index.ts)
 
 <a id="deepseek-aidsh-headless"></a>
 
@@ -811,7 +813,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-hooks-claude-code`
 
-需要:`shell`
+需要:`shell` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: where the CC hook config lives + substitution roots. */
@@ -843,13 +845,13 @@ export interface Config {
 }
 ```
 
-来源:[`packages/hooks/hooks-claude-code/src/index.ts:45`](../packages/hooks/hooks-claude-code/src/index.ts)
+来源:[`packages/hooks/hooks-claude-code/src/index.ts:46`](../packages/hooks/hooks-claude-code/src/index.ts)
 
 <a id="deepseek-aidsh-hooks-codex"></a>
 
 ## `@deepseek-ai/dsh-hooks-codex`
 
-需要:`shell`
+需要:`shell` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: where the Codex hooks.json lives + the model name for payloads. */
@@ -870,7 +872,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/hooks/hooks-codex/src/index.ts:44`](../packages/hooks/hooks-codex/src/index.ts)
+来源:[`packages/hooks/hooks-codex/src/index.ts:45`](../packages/hooks/hooks-codex/src/index.ts)
 
 <a id="deepseek-aidsh-host-directory-picker-browse"></a>
 
@@ -1387,14 +1389,14 @@ export interface ReplayModelConfig {
 
 ## `@deepseek-ai/dsh-llm-retry`
 
-需要:`agents`
+需要:`agents` · `sessionProjections`
 
 ```ts config-catalog
 /** This policy executor has no config; providers own `retryPolicy`. */
 export type Config = Readonly<Record<string, never>>
 ```
 
-来源:[`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts)
+来源:[`packages/llm/llm-retry/src/index.ts:25`](../packages/llm/llm-retry/src/index.ts)
 
 <a id="deepseek-aidsh-lsp-stdio"></a>
 
@@ -1531,7 +1533,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-permission-presets`
 
-需要:`shell` · `approval` · `sessions`
+需要:`shell` · `approval` · `sessions` · `sessionProjections`
 
 ```ts config-catalog
 /** The {@link PermissionPresetService} config: preset table and composition default. */
@@ -1564,7 +1566,7 @@ export interface PresetSpec {
 
 依赖:[`ApprovalPolicy`](subsystems/approval.zh.md) · [`SandboxMode`](subsystems/sandbox.zh.md)
 
-来源:[`packages/interaction/permission-presets/src/index.ts:156`](../packages/interaction/permission-presets/src/index.ts)
+来源:[`packages/interaction/permission-presets/src/index.ts:143`](../packages/interaction/permission-presets/src/index.ts)
 
 <a id="deepseek-aidsh-persona"></a>
 
@@ -1594,7 +1596,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-plan-mode`
 
-需要:`tools` · `systemPrompt`
+需要:`tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment-owned plan guidance. */
@@ -1604,7 +1606,7 @@ export interface PlanModeConfig {
 }
 ```
 
-来源:[`packages/plan/plan-mode/src/index.ts:70`](../packages/plan/plan-mode/src/index.ts)
+来源:[`packages/plan/plan-mode/src/index.ts:64`](../packages/plan/plan-mode/src/index.ts)
 
 <a id="deepseek-aidsh-plugin-package-inventory-deepseek"></a>
 
@@ -1747,6 +1749,8 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-sandbox-policy`
 
+需要:`sessionProjections`
+
 ```ts config-catalog
 /**
  * Plugin config: the deployment's sandbox default. All optional — `Config`
@@ -1768,7 +1772,7 @@ export interface Config {
 
 依赖:[`SandboxMode`](subsystems/sandbox.zh.md)
 
-来源:[`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts)
+来源:[`packages/sandbox/sandbox-policy/src/index.ts:70`](../packages/sandbox/sandbox-policy/src/index.ts)
 
 <a id="deepseek-aidsh-sdk-app"></a>
 
@@ -1849,7 +1853,7 @@ export type SessionLogCompressionLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
 
 ## `@deepseek-ai/dsh-session-persistence-jsonl`
 
-需要:`sessions`
+需要:`sessions` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config: where the JSONL backend keeps its session logs, and the packed-row write switch. */
@@ -2065,7 +2069,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts)
+来源:[`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
 
 <a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
 
@@ -2216,7 +2220,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-spill-policy`
 
-需要:`tools`
+需要:`tools` · `sessionProjections`
 
 ```ts config-catalog
 /** Plugin config. */
@@ -2572,7 +2576,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-terminal-bash`
 
-需要:`terminals` · `sandboxPolicy` · `subprocess`
+需要:`terminals` · `sandboxPolicy` · `sessionProjections` · `subprocess`
 
 ```ts config-catalog
 /** Public plugin configuration. */
@@ -2622,7 +2626,7 @@ export type ShellDialect = 'bash' | 'pwsh'
 
 ## `@deepseek-ai/dsh-time-context`
 
-需要:`agents`
+需要:`agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Request-preparation clock formatting and append scheduling. Invalid values fail plugin load. */
@@ -2634,13 +2638,13 @@ export interface Config {
 }
 ```
 
-来源:[`packages/context/time-context/src/index.ts:27`](../packages/context/time-context/src/index.ts)
+来源:[`packages/context/time-context/src/index.ts:48`](../packages/context/time-context/src/index.ts)
 
 <a id="deepseek-aidsh-tmux-context"></a>
 
 ## `@deepseek-ai/dsh-tmux-context`
 
-需要:`agents`
+需要:`agents` · `sessionProjections`
 
 ```ts config-catalog
 /** Per-turn tmux-location scheduling. Invalid values fail plugin load. */
@@ -2650,12 +2654,14 @@ export interface Config {
 }
 ```
 
-来源:[`packages/context/tmux-context/src/index.ts:34`](../packages/context/tmux-context/src/index.ts)
+来源:[`packages/context/tmux-context/src/index.ts:36`](../packages/context/tmux-context/src/index.ts)
 
 <a id="deepseek-aidsh-token-meter"></a>
 
 ## `@deepseek-ai/dsh-token-meter`
 
+需要:`sessionProjections`
+
 ```ts config-catalog
 /** Token-meter plugin configuration; the fixed estimator has no settings. */
 export type TokenMeterConfig = Record<string, never>
@@ -2762,7 +2768,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-tool-goal`
 
-需要:`agents` · `goals` · `tools` · `systemPrompt`
+需要:`agents` · `goals` · `tools` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Model policy and hard lower bounds for goal-state updates. */
@@ -2892,7 +2898,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-tool-session-query`
 
-需要:`tools` · `systemPrompt` · `sessionQuery`
+需要:`tools` · `systemPrompt` · `sessionQuery` · `sessionProjections`
 
 ```ts config-catalog
 /** Deployment-owned search count and timeout bounds. */
@@ -2944,7 +2950,7 @@ export interface Config {
 
 ## `@deepseek-ai/dsh-tool-subagent`
 
-需要:`tools` · `subagents` · `systemPrompt`
+需要:`tools` · `subagents` · `systemPrompt` · `sessionProjections`
 
 ```ts config-catalog
 /** Config: which registered provider this tool delegates to, plus child defaults. */
@@ -3008,7 +3014,7 @@ export interface Config {
 
 依赖:[`AgentOptions`](subsystems/core.zh.md)
 
-来源:[`packages/subagent/tool-subagent/src/index.ts:48`](../packages/subagent/tool-subagent/src/index.ts)
+来源:[`packages/subagent/tool-subagent/src/index.ts:50`](../packages/subagent/tool-subagent/src/index.ts)
 
 <a id="deepseek-aidsh-tool-subagent-report"></a>
 

+ 2 - 2
docs/cookbook/adding-a-package.i18n.yaml

@@ -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 docs/cookbook/adding-a-package.md
-adding-a-package.md: f228fa8871284b0473bcbb461157a142161abb81
-adding-a-package.zh.md: d60472cd9abc2a1fe7b5589ee8b6ca07a3c2f4b9
+adding-a-package.md: f38c0d3f3bdfe126ca1de4801548c546fc5994cc
+adding-a-package.zh.md: 11f6faa75e757b769ef84a039cef05afd1324a95

+ 0 - 1
docs/cookbook/adding-a-package.md

@@ -32,7 +32,6 @@ In-package relative imports use explicit `.ts` specifiers in source (for example
 |---|---|
 | `tsconfig.base.json` | no edit for an existing group; for a new group, add a `./packages/<group>/*/src` candidate to the `@deepseek-ai/dsh-*` wildcard |
 | `tsconfig.host.json` (Host package) or `tsconfig.client.json` (Client package) | add `{ "path": "./packages/<group>/<pkg>" }` to `references` — an ordinary package belongs to exactly one aggregate, never both. `api/remotes` uses a repository-specific split because the Host generates a contract that the Client consumes in a later phase; new packages must not copy it ([layout](../development.md#typescript-project-layout)) |
-| `knip.json` | only if the package has entrypoints that repository discovery does not already cover |
 
 A `packages/client/*` package additionally extends `tsconfig.base.client.json` instead of `tsconfig.base.json`, and a client plugin package declares `dsh.client` in package.json, exports `./client`, and calls the shared tsdown preset (`packages/client/tsdown.client.ts`) — see [packages/client/AGENTS.md](../../packages/client/AGENTS.md) for the client-side contract.
 

+ 0 - 1
docs/cookbook/adding-a-package.zh.md

@@ -32,7 +32,6 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c
 |---|---|
 | `tsconfig.base.json` | 已有分组无需编辑;新分组需为 `@deepseek-ai/dsh-*` 通配符添加 `./packages/<group>/*/src` 候选路径 |
 | `tsconfig.host.json`(Host 包)或 `tsconfig.client.json`(Client 包) | 在 `references` 中添加 `{ "path": "./packages/<group>/<pkg>" }`——普通包恰好属于一个 aggregate,绝不两个都加。`api/remotes` 因 Host 生成约定与 Client 消费约定之间存在顺序依赖而使用仓库专属拆分,新增包不得仿照([布局](../development.zh.md#typescript-project-layout)) |
-| `knip.json` | 仅当包有仓库发现机制尚未覆盖的入口时需要 |
 
 `packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dsh.client`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。
 

+ 2 - 2
docs/event-producer-consumer.i18n.yaml

@@ -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 docs/event-producer-consumer.md
-event-producer-consumer.md: 7558a4652e253ce2c29a5450b5ed921a6a037316
-event-producer-consumer.zh.md: a9e15510b44157184f985ddba29bf53bee75196e
+event-producer-consumer.md: 414bfdcf83a23ee756bddf2ceaa2c307fa6bd31c
+event-producer-consumer.zh.md: 8c9ad37ae0d7e278ad6c0459e740a6d1d85183a5

+ 1 - 1
docs/event-producer-consumer.md

@@ -7,7 +7,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
 
 | Event | Mode | Declared in | Dispatchers | Listeners |
 | --- | --- | --- | --- | --- |
-| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
+| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
 | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:92`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
 | `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:166`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
 | `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:175`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |

+ 1 - 1
docs/event-producer-consumer.zh.md

@@ -9,7 +9,7 @@
 
 | 事件 | 模式 | 声明位置 | 派发方 | 监听方 |
 | --- | --- | --- | --- | --- |
-| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
+| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
 | `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:92`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
 | `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:166`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
 | `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:175`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |

+ 2 - 2
docs/module-graph.i18n.yaml

@@ -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 docs/module-graph.md
-module-graph.md: 345c2a39528ffed5b476390ce997dca34854c73a
-module-graph.zh.md: 41b6449be03a85556adc0d7fb10fe597aba631c4
+module-graph.md: 26cc6bf981c8628c4a3a2bb5850582174f5521ac
+module-graph.zh.md: 99f825e274eb84f3b74dd4c5a5e333d5ffb58e78

+ 177 - 161
docs/module-graph.md

@@ -460,12 +460,6 @@ flowchart TD
   pkg_lsp --> pkg_brand
   pkg_lsp --> pkg_invariants
   pkg_lsp --> pkg_llm
-  pkg_agent --> pkg_invariants
-  pkg_agent --> pkg_llm
-  pkg_agent --> pkg_scope
-  pkg_agent --> pkg_session
-  pkg_agent --> pkg_system_prompt
-  pkg_agent --> pkg_typert_protocol
   pkg_skill_badge --> pkg_invariants
   pkg_skill_badge --> pkg_skill
   pkg_web_fetch_http --> pkg_invariants
@@ -508,11 +502,87 @@ flowchart TD
   pkg_settings --> pkg_session
   pkg_session_snapshot --> pkg_invariants
   pkg_session_snapshot --> pkg_session
+  pkg_agent --> pkg_invariants
+  pkg_agent --> pkg_llm
+  pkg_agent --> pkg_scope
+  pkg_agent --> pkg_session
+  pkg_agent --> pkg_session_projection
+  pkg_agent --> pkg_system_prompt
+  pkg_agent --> pkg_typert_protocol
+  pkg_fs --> pkg_brand
+  pkg_fs --> pkg_invariants
+  pkg_fs --> pkg_llm
+  pkg_fs --> pkg_sandbox
+  pkg_spill_local --> pkg_invariants
+  pkg_spill_local --> pkg_spill
+  pkg_message_feedback --> pkg_brand
+  pkg_message_feedback --> pkg_invariants
+  pkg_message_feedback --> pkg_llm
+  pkg_message_feedback --> pkg_session
+  pkg_message_feedback --> pkg_session_persistence
+  pkg_message_feedback --> pkg_storage_domain
+  pkg_message_feedback --> pkg_typert_protocol
+  pkg_sandbox_local --> pkg_invariants
+  pkg_sandbox_local --> pkg_llm
+  pkg_sandbox_local --> pkg_sandbox
+  pkg_sandbox_local --> pkg_session
+  pkg_session_persistence_jsonl --> pkg_invariants
+  pkg_session_persistence_jsonl --> pkg_session
+  pkg_session_persistence_jsonl --> pkg_session_persistence
+  pkg_session_persistence_sqlite --> pkg_invariants
+  pkg_session_persistence_sqlite --> pkg_llm
+  pkg_session_persistence_sqlite --> pkg_session
+  pkg_session_persistence_sqlite --> pkg_session_persistence
+  pkg_session_projection_cache --> pkg_invariants
+  pkg_session_projection_cache --> pkg_session
+  pkg_session_projection_cache --> pkg_session_projection
+  pkg_session_projection_cache --> pkg_storage_domain
+  pkg_session_stats --> pkg_invariants
+  pkg_session_stats --> pkg_llm
+  pkg_session_stats --> pkg_session
+  pkg_session_stats --> pkg_session_projection
+  pkg_settings_file --> pkg_atomic_write
+  pkg_settings_file --> pkg_home_paths
+  pkg_settings_file --> pkg_invariants
+  pkg_settings_file --> pkg_settings
+  pkg_shell --> pkg_invariants
+  pkg_shell --> pkg_sandbox
+  pkg_shell --> pkg_settings
+  pkg_shell --> pkg_subprocess
+  pkg_workspace --> pkg_brand
+  pkg_workspace --> pkg_invariants
+  pkg_workspace --> pkg_session
+  pkg_workspace --> pkg_session_persistence
+  pkg_workspace --> pkg_storage
+  pkg_workspace --> pkg_storage_domain
+  pkg_llm_deepseek --> pkg_anonymous_user_id
+  pkg_llm_deepseek --> pkg_atomic_write
+  pkg_llm_deepseek --> pkg_attachment
+  pkg_llm_deepseek --> pkg_brand
+  pkg_llm_deepseek --> pkg_credentials
+  pkg_llm_deepseek --> pkg_deepseek_llm_api_extensions
+  pkg_llm_deepseek --> pkg_fs
+  pkg_llm_deepseek --> pkg_home_paths
+  pkg_llm_deepseek --> pkg_invariants
+  pkg_llm_deepseek --> pkg_launch_environment
+  pkg_llm_deepseek --> pkg_llm
+  pkg_llm_deepseek --> pkg_settings
+  pkg_llm_deepseek --> pkg_timeout
+  pkg_llm_pi_ai --> pkg_attachment
+  pkg_llm_pi_ai --> pkg_authorization
+  pkg_llm_pi_ai --> pkg_credentials
+  pkg_llm_pi_ai --> pkg_fs
+  pkg_llm_pi_ai --> pkg_invariants
+  pkg_llm_pi_ai --> pkg_launch_environment
+  pkg_llm_pi_ai --> pkg_llm
+  pkg_llm_pi_ai --> pkg_settings
+  pkg_llm_pi_ai --> pkg_timeout
   pkg_llm_retry --> pkg_agent
   pkg_llm_retry --> pkg_brand
   pkg_llm_retry --> pkg_invariants
   pkg_llm_retry --> pkg_llm
   pkg_llm_retry --> pkg_session
+  pkg_llm_retry --> pkg_session_projection
   pkg_llm_retry --> pkg_timeout
   pkg_agent_default_model --> pkg_agent
   pkg_agent_default_model --> pkg_invariants
@@ -526,10 +596,14 @@ flowchart TD
   pkg_goal --> pkg_session
   pkg_goal --> pkg_session_projection
   pkg_goal --> pkg_typert_protocol
-  pkg_fs --> pkg_brand
-  pkg_fs --> pkg_invariants
-  pkg_fs --> pkg_llm
-  pkg_fs --> pkg_sandbox
+  pkg_fs_local --> pkg_fs
+  pkg_fs_local --> pkg_invariants
+  pkg_fs_observation_policy --> pkg_fs
+  pkg_fs_observation_policy --> pkg_invariants
+  pkg_skill_filesystem --> pkg_fs
+  pkg_skill_filesystem --> pkg_home_paths
+  pkg_skill_filesystem --> pkg_invariants
+  pkg_skill_filesystem --> pkg_skill
   pkg_web_search_deepseek --> pkg_agent
   pkg_web_search_deepseek --> pkg_credentials
   pkg_web_search_deepseek --> pkg_invariants
@@ -537,21 +611,24 @@ flowchart TD
   pkg_web_search_deepseek --> pkg_session
   pkg_web_search_deepseek --> pkg_settings
   pkg_web_search_deepseek --> pkg_web
-  pkg_spill_local --> pkg_invariants
-  pkg_spill_local --> pkg_spill
+  pkg_hook_protocol --> pkg_invariants
+  pkg_hook_protocol --> pkg_session
+  pkg_hook_protocol --> pkg_shell
   pkg_file_reference --> pkg_agent
   pkg_file_reference --> pkg_invariants
   pkg_time_context --> pkg_agent
   pkg_time_context --> pkg_invariants
   pkg_time_context --> pkg_llm
   pkg_time_context --> pkg_session
-  pkg_message_feedback --> pkg_brand
-  pkg_message_feedback --> pkg_invariants
-  pkg_message_feedback --> pkg_llm
-  pkg_message_feedback --> pkg_session
-  pkg_message_feedback --> pkg_session_persistence
-  pkg_message_feedback --> pkg_storage_domain
-  pkg_message_feedback --> pkg_typert_protocol
+  pkg_time_context --> pkg_session_projection
+  pkg_tmux_context --> pkg_agent
+  pkg_tmux_context --> pkg_invariants
+  pkg_tmux_context --> pkg_session
+  pkg_tmux_context --> pkg_session_projection
+  pkg_tmux_context --> pkg_shell
+  pkg_fs_e2b --> pkg_e2b
+  pkg_fs_e2b --> pkg_fs
+  pkg_fs_e2b --> pkg_invariants
   pkg_commands --> pkg_agent
   pkg_commands --> pkg_attachment
   pkg_commands --> pkg_brand
@@ -575,46 +652,38 @@ flowchart TD
   pkg_jobs --> pkg_brand
   pkg_jobs --> pkg_invariants
   pkg_jobs --> pkg_session
-  pkg_sandbox_local --> pkg_invariants
-  pkg_sandbox_local --> pkg_llm
-  pkg_sandbox_local --> pkg_sandbox
-  pkg_sandbox_local --> pkg_session
+  pkg_lsp_stdio --> pkg_brand
+  pkg_lsp_stdio --> pkg_fs
+  pkg_lsp_stdio --> pkg_invariants
+  pkg_lsp_stdio --> pkg_llm
+  pkg_lsp_stdio --> pkg_lsp
+  pkg_lsp_stdio --> pkg_subprocess
+  pkg_lsp_stdio --> pkg_timeout
   pkg_sandbox_policy --> pkg_agent
   pkg_sandbox_policy --> pkg_invariants
   pkg_sandbox_policy --> pkg_sandbox
   pkg_sandbox_policy --> pkg_session
+  pkg_sandbox_policy --> pkg_session_projection
   pkg_sandbox_policy --> pkg_system_prompt
-  pkg_session_persistence_jsonl --> pkg_invariants
-  pkg_session_persistence_jsonl --> pkg_session
-  pkg_session_persistence_jsonl --> pkg_session_persistence
-  pkg_session_persistence_sqlite --> pkg_invariants
-  pkg_session_persistence_sqlite --> pkg_llm
-  pkg_session_persistence_sqlite --> pkg_session
-  pkg_session_persistence_sqlite --> pkg_session_persistence
-  pkg_session_projection_cache --> pkg_invariants
-  pkg_session_projection_cache --> pkg_session
-  pkg_session_projection_cache --> pkg_session_projection
-  pkg_session_projection_cache --> pkg_storage_domain
-  pkg_session_stats --> pkg_invariants
-  pkg_session_stats --> pkg_llm
-  pkg_session_stats --> pkg_session
-  pkg_session_stats --> pkg_session_projection
   pkg_session_telemetry --> pkg_agent
   pkg_session_telemetry --> pkg_invariants
   pkg_session_telemetry --> pkg_session
+  pkg_session_title --> pkg_agent
   pkg_session_title --> pkg_brand
   pkg_session_title --> pkg_invariants
   pkg_session_title --> pkg_llm
   pkg_session_title --> pkg_session
   pkg_session_title --> pkg_session_projection
-  pkg_settings_file --> pkg_atomic_write
-  pkg_settings_file --> pkg_home_paths
-  pkg_settings_file --> pkg_invariants
-  pkg_settings_file --> pkg_settings
-  pkg_shell --> pkg_invariants
-  pkg_shell --> pkg_sandbox
-  pkg_shell --> pkg_settings
-  pkg_shell --> pkg_subprocess
+  pkg_bash_local --> pkg_invariants
+  pkg_bash_local --> pkg_settings
+  pkg_bash_local --> pkg_shell
+  pkg_bash_local --> pkg_subprocess
+  pkg_bash_local --> pkg_timeout
+  pkg_pwsh_local --> pkg_invariants
+  pkg_pwsh_local --> pkg_settings
+  pkg_pwsh_local --> pkg_shell
+  pkg_pwsh_local --> pkg_subprocess
+  pkg_pwsh_local --> pkg_timeout
   pkg_terminal --> pkg_agent
   pkg_terminal --> pkg_brand
   pkg_terminal --> pkg_invariants
@@ -627,34 +696,6 @@ flowchart TD
   pkg_workflow --> pkg_invariants
   pkg_workflow --> pkg_llm
   pkg_workflow --> pkg_session
-  pkg_workspace --> pkg_brand
-  pkg_workspace --> pkg_invariants
-  pkg_workspace --> pkg_session
-  pkg_workspace --> pkg_session_persistence
-  pkg_workspace --> pkg_storage
-  pkg_workspace --> pkg_storage_domain
-  pkg_llm_deepseek --> pkg_anonymous_user_id
-  pkg_llm_deepseek --> pkg_atomic_write
-  pkg_llm_deepseek --> pkg_attachment
-  pkg_llm_deepseek --> pkg_brand
-  pkg_llm_deepseek --> pkg_credentials
-  pkg_llm_deepseek --> pkg_deepseek_llm_api_extensions
-  pkg_llm_deepseek --> pkg_fs
-  pkg_llm_deepseek --> pkg_home_paths
-  pkg_llm_deepseek --> pkg_invariants
-  pkg_llm_deepseek --> pkg_launch_environment
-  pkg_llm_deepseek --> pkg_llm
-  pkg_llm_deepseek --> pkg_settings
-  pkg_llm_deepseek --> pkg_timeout
-  pkg_llm_pi_ai --> pkg_attachment
-  pkg_llm_pi_ai --> pkg_authorization
-  pkg_llm_pi_ai --> pkg_credentials
-  pkg_llm_pi_ai --> pkg_fs
-  pkg_llm_pi_ai --> pkg_invariants
-  pkg_llm_pi_ai --> pkg_launch_environment
-  pkg_llm_pi_ai --> pkg_llm
-  pkg_llm_pi_ai --> pkg_settings
-  pkg_llm_pi_ai --> pkg_timeout
   pkg_tools --> pkg_agent
   pkg_tools --> pkg_code_runtime
   pkg_tools --> pkg_invariants
@@ -672,17 +713,11 @@ flowchart TD
   pkg_goal_round_driver --> pkg_invariants
   pkg_goal_round_driver --> pkg_llm
   pkg_goal_round_driver --> pkg_session
-  pkg_fs_local --> pkg_fs
-  pkg_fs_local --> pkg_invariants
-  pkg_fs_observation_policy --> pkg_fs
-  pkg_fs_observation_policy --> pkg_invariants
-  pkg_skill_filesystem --> pkg_fs
-  pkg_skill_filesystem --> pkg_home_paths
-  pkg_skill_filesystem --> pkg_invariants
-  pkg_skill_filesystem --> pkg_skill
-  pkg_hook_protocol --> pkg_invariants
-  pkg_hook_protocol --> pkg_session
-  pkg_hook_protocol --> pkg_shell
+  pkg_fs_sandbox --> pkg_fs
+  pkg_fs_sandbox --> pkg_fs_local
+  pkg_fs_sandbox --> pkg_invariants
+  pkg_fs_sandbox --> pkg_sandbox
+  pkg_fs_sandbox --> pkg_sandbox_policy
   pkg_headless --> pkg_agent
   pkg_headless --> pkg_agent_default_model
   pkg_headless --> pkg_invariants
@@ -693,13 +728,6 @@ flowchart TD
   pkg_compaction --> pkg_invariants
   pkg_compaction --> pkg_llm
   pkg_compaction --> pkg_session
-  pkg_tmux_context --> pkg_agent
-  pkg_tmux_context --> pkg_invariants
-  pkg_tmux_context --> pkg_session
-  pkg_tmux_context --> pkg_shell
-  pkg_fs_e2b --> pkg_e2b
-  pkg_fs_e2b --> pkg_fs
-  pkg_fs_e2b --> pkg_invariants
   pkg_command_feedback --> pkg_anonymous_user_id
   pkg_command_feedback --> pkg_commands
   pkg_command_feedback --> pkg_invariants
@@ -719,33 +747,27 @@ flowchart TD
   pkg_jobs_local --> pkg_jobs
   pkg_jobs_local --> pkg_scope
   pkg_jobs_local --> pkg_timeout
-  pkg_lsp_stdio --> pkg_brand
-  pkg_lsp_stdio --> pkg_fs
-  pkg_lsp_stdio --> pkg_invariants
-  pkg_lsp_stdio --> pkg_llm
-  pkg_lsp_stdio --> pkg_lsp
-  pkg_lsp_stdio --> pkg_subprocess
-  pkg_lsp_stdio --> pkg_timeout
   pkg_session_title_llm --> pkg_invariants
   pkg_session_title_llm --> pkg_llm
   pkg_session_title_llm --> pkg_session
   pkg_session_title_llm --> pkg_session_title
   pkg_session_title_llm --> pkg_timeout
-  pkg_bash_local --> pkg_invariants
-  pkg_bash_local --> pkg_settings
-  pkg_bash_local --> pkg_shell
-  pkg_bash_local --> pkg_subprocess
-  pkg_bash_local --> pkg_timeout
-  pkg_pwsh_local --> pkg_invariants
-  pkg_pwsh_local --> pkg_settings
-  pkg_pwsh_local --> pkg_shell
-  pkg_pwsh_local --> pkg_subprocess
-  pkg_pwsh_local --> pkg_timeout
+  pkg_bash_sandbox --> pkg_bash_local
+  pkg_bash_sandbox --> pkg_invariants
+  pkg_bash_sandbox --> pkg_sandbox
+  pkg_bash_sandbox --> pkg_sandbox_policy
+  pkg_bash_sandbox --> pkg_shell
+  pkg_pwsh_sandbox --> pkg_invariants
+  pkg_pwsh_sandbox --> pkg_pwsh_local
+  pkg_pwsh_sandbox --> pkg_sandbox
+  pkg_pwsh_sandbox --> pkg_sandbox_policy
+  pkg_pwsh_sandbox --> pkg_shell
   pkg_terminal_bash --> pkg_agent
   pkg_terminal_bash --> pkg_invariants
   pkg_terminal_bash --> pkg_sandbox
   pkg_terminal_bash --> pkg_sandbox_policy
   pkg_terminal_bash --> pkg_session
+  pkg_terminal_bash --> pkg_session_projection
   pkg_terminal_bash --> pkg_subprocess
   pkg_terminal_bash --> pkg_terminal
   pkg_token_meter --> pkg_compaction
@@ -760,6 +782,7 @@ flowchart TD
   pkg_agent_loop --> pkg_scope
   pkg_agent_loop --> pkg_session
   pkg_agent_loop --> pkg_session_persistence
+  pkg_agent_loop --> pkg_session_projection
   pkg_agent_loop --> pkg_settings
   pkg_agent_loop --> pkg_system_prompt
   pkg_agent_loop --> pkg_tools
@@ -770,13 +793,9 @@ flowchart TD
   pkg_tool_goal --> pkg_invariants
   pkg_tool_goal --> pkg_llm
   pkg_tool_goal --> pkg_session
+  pkg_tool_goal --> pkg_session_projection
   pkg_tool_goal --> pkg_system_prompt
   pkg_tool_goal --> pkg_tools
-  pkg_fs_sandbox --> pkg_fs
-  pkg_fs_sandbox --> pkg_fs_local
-  pkg_fs_sandbox --> pkg_invariants
-  pkg_fs_sandbox --> pkg_sandbox
-  pkg_fs_sandbox --> pkg_sandbox_policy
   pkg_tool_fs --> pkg_attachment
   pkg_tool_fs --> pkg_fs
   pkg_tool_fs --> pkg_invariants
@@ -837,6 +856,7 @@ flowchart TD
   pkg_hooks_codex --> pkg_llm
   pkg_hooks_codex --> pkg_session
   pkg_hooks_codex --> pkg_session_persistence
+  pkg_hooks_codex --> pkg_session_projection
   pkg_hooks_codex --> pkg_tools
   pkg_command_compact --> pkg_commands
   pkg_command_compact --> pkg_compaction
@@ -847,6 +867,7 @@ flowchart TD
   pkg_agent_instructions --> pkg_invariants
   pkg_agent_instructions --> pkg_llm
   pkg_agent_instructions --> pkg_session
+  pkg_agent_instructions --> pkg_session_projection
   pkg_agent_instructions --> pkg_tools
   pkg_file_reference_local --> pkg_agent
   pkg_file_reference_local --> pkg_file_reference
@@ -932,16 +953,6 @@ flowchart TD
   pkg_session_title_first_prompt_llm --> pkg_session
   pkg_session_title_first_prompt_llm --> pkg_session_title
   pkg_session_title_first_prompt_llm --> pkg_session_title_llm
-  pkg_bash_sandbox --> pkg_bash_local
-  pkg_bash_sandbox --> pkg_invariants
-  pkg_bash_sandbox --> pkg_sandbox
-  pkg_bash_sandbox --> pkg_sandbox_policy
-  pkg_bash_sandbox --> pkg_shell
-  pkg_pwsh_sandbox --> pkg_invariants
-  pkg_pwsh_sandbox --> pkg_pwsh_local
-  pkg_pwsh_sandbox --> pkg_sandbox
-  pkg_pwsh_sandbox --> pkg_sandbox_policy
-  pkg_pwsh_sandbox --> pkg_shell
   pkg_shell_env --> pkg_home_paths
   pkg_shell_env --> pkg_invariants
   pkg_shell_env --> pkg_session_persistence
@@ -1095,9 +1106,11 @@ flowchart TD
   pkg_session_query_sqlite --> pkg_session
   pkg_session_query_sqlite --> pkg_session_persistence
   pkg_session_query_sqlite --> pkg_session_query
+  pkg_tool_session_query --> pkg_agent
   pkg_tool_session_query --> pkg_invariants
   pkg_tool_session_query --> pkg_llm
   pkg_tool_session_query --> pkg_session
+  pkg_tool_session_query --> pkg_session_projection
   pkg_tool_session_query --> pkg_session_query
   pkg_tool_session_query --> pkg_system_prompt
   pkg_tool_session_query --> pkg_timeout
@@ -1192,6 +1205,7 @@ flowchart TD
   pkg_tool_subagent --> pkg_llm
   pkg_tool_subagent --> pkg_scope
   pkg_tool_subagent --> pkg_session
+  pkg_tool_subagent --> pkg_session_projection
   pkg_tool_subagent --> pkg_settings
   pkg_tool_subagent --> pkg_subagent
   pkg_tool_subagent --> pkg_system_prompt
@@ -1212,6 +1226,7 @@ flowchart TD
   pkg_hooks_claude_code --> pkg_llm
   pkg_hooks_claude_code --> pkg_session
   pkg_hooks_claude_code --> pkg_session_persistence
+  pkg_hooks_claude_code --> pkg_session_projection
   pkg_hooks_claude_code --> pkg_subagent
   pkg_hooks_claude_code --> pkg_tools
   pkg_api_session_controller --> pkg_agent
@@ -1253,6 +1268,7 @@ flowchart TD
   pkg_experimental_agent_team --> pkg_llm
   pkg_experimental_agent_team --> pkg_session
   pkg_experimental_agent_team --> pkg_session_persistence
+  pkg_experimental_agent_team --> pkg_session_projection
   pkg_experimental_agent_team --> pkg_subagent
   pkg_experimental_agent_team --> pkg_typert_protocol
   pkg_sdk_protocol --> pkg_invariants
@@ -1772,7 +1788,6 @@ flowchart TD
 | [`web`](../packages/web/web) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
 | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
@@ -1787,59 +1802,62 @@ flowchart TD
 | [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
-| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
-| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
+| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
-| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) |
-| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
-| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |
-| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
-| [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
-| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
-| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain) |
 | [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
-| [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`session-title`](../packages/session/session-title) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
 | [`settings-file`](../packages/settings/settings-file) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
 | [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
-| [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
-| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`credentials`](../packages/credentials/credentials), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
 | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
-| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
-| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) |
+| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
+| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
+| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
 | [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
+| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
+| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) |
+| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |
+| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
+| [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
+| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) |
+| [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
+| [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
+| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
+| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
 | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
-| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
-| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`user-approval`](../packages/interaction/user-approval) |
 | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) |
-| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) |
-| [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
-| [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
-| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
+| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
+| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
+| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
 | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
-| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
+| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
-| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
+| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
 | [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
@@ -1848,9 +1866,9 @@ flowchart TD
 | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) |
 | [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
-| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
+| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
+| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
 | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
@@ -1865,8 +1883,6 @@ flowchart TD
 | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
-| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
-| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
 | [`shell-env`](../packages/shell/shell-env) | `shell` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-persistence`](../packages/session/session-persistence), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |
 | [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
@@ -1887,7 +1903,7 @@ flowchart TD
 | [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
 | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
 | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
-| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
+| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`api-gateway`](../packages/api/gateway) | `api` | [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-registry`](../packages/typert/registry) |
 | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
 | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`typert-protocol`](../packages/typert/protocol) |
@@ -1899,13 +1915,13 @@ flowchart TD
 | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
+| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
 | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
+| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
 | [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
 | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) |
-| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
+| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
 | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
 | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |

+ 178 - 162
docs/module-graph.zh.md

@@ -462,12 +462,6 @@ flowchart TD
   pkg_lsp --> pkg_brand
   pkg_lsp --> pkg_invariants
   pkg_lsp --> pkg_llm
-  pkg_agent --> pkg_invariants
-  pkg_agent --> pkg_llm
-  pkg_agent --> pkg_scope
-  pkg_agent --> pkg_session
-  pkg_agent --> pkg_system_prompt
-  pkg_agent --> pkg_typert_protocol
   pkg_skill_badge --> pkg_invariants
   pkg_skill_badge --> pkg_skill
   pkg_web_fetch_http --> pkg_invariants
@@ -510,11 +504,87 @@ flowchart TD
   pkg_settings --> pkg_session
   pkg_session_snapshot --> pkg_invariants
   pkg_session_snapshot --> pkg_session
+  pkg_agent --> pkg_invariants
+  pkg_agent --> pkg_llm
+  pkg_agent --> pkg_scope
+  pkg_agent --> pkg_session
+  pkg_agent --> pkg_session_projection
+  pkg_agent --> pkg_system_prompt
+  pkg_agent --> pkg_typert_protocol
+  pkg_fs --> pkg_brand
+  pkg_fs --> pkg_invariants
+  pkg_fs --> pkg_llm
+  pkg_fs --> pkg_sandbox
+  pkg_spill_local --> pkg_invariants
+  pkg_spill_local --> pkg_spill
+  pkg_message_feedback --> pkg_brand
+  pkg_message_feedback --> pkg_invariants
+  pkg_message_feedback --> pkg_llm
+  pkg_message_feedback --> pkg_session
+  pkg_message_feedback --> pkg_session_persistence
+  pkg_message_feedback --> pkg_storage_domain
+  pkg_message_feedback --> pkg_typert_protocol
+  pkg_sandbox_local --> pkg_invariants
+  pkg_sandbox_local --> pkg_llm
+  pkg_sandbox_local --> pkg_sandbox
+  pkg_sandbox_local --> pkg_session
+  pkg_session_persistence_jsonl --> pkg_invariants
+  pkg_session_persistence_jsonl --> pkg_session
+  pkg_session_persistence_jsonl --> pkg_session_persistence
+  pkg_session_persistence_sqlite --> pkg_invariants
+  pkg_session_persistence_sqlite --> pkg_llm
+  pkg_session_persistence_sqlite --> pkg_session
+  pkg_session_persistence_sqlite --> pkg_session_persistence
+  pkg_session_projection_cache --> pkg_invariants
+  pkg_session_projection_cache --> pkg_session
+  pkg_session_projection_cache --> pkg_session_projection
+  pkg_session_projection_cache --> pkg_storage_domain
+  pkg_session_stats --> pkg_invariants
+  pkg_session_stats --> pkg_llm
+  pkg_session_stats --> pkg_session
+  pkg_session_stats --> pkg_session_projection
+  pkg_settings_file --> pkg_atomic_write
+  pkg_settings_file --> pkg_home_paths
+  pkg_settings_file --> pkg_invariants
+  pkg_settings_file --> pkg_settings
+  pkg_shell --> pkg_invariants
+  pkg_shell --> pkg_sandbox
+  pkg_shell --> pkg_settings
+  pkg_shell --> pkg_subprocess
+  pkg_workspace --> pkg_brand
+  pkg_workspace --> pkg_invariants
+  pkg_workspace --> pkg_session
+  pkg_workspace --> pkg_session_persistence
+  pkg_workspace --> pkg_storage
+  pkg_workspace --> pkg_storage_domain
+  pkg_llm_deepseek --> pkg_anonymous_user_id
+  pkg_llm_deepseek --> pkg_atomic_write
+  pkg_llm_deepseek --> pkg_attachment
+  pkg_llm_deepseek --> pkg_brand
+  pkg_llm_deepseek --> pkg_credentials
+  pkg_llm_deepseek --> pkg_deepseek_llm_api_extensions
+  pkg_llm_deepseek --> pkg_fs
+  pkg_llm_deepseek --> pkg_home_paths
+  pkg_llm_deepseek --> pkg_invariants
+  pkg_llm_deepseek --> pkg_launch_environment
+  pkg_llm_deepseek --> pkg_llm
+  pkg_llm_deepseek --> pkg_settings
+  pkg_llm_deepseek --> pkg_timeout
+  pkg_llm_pi_ai --> pkg_attachment
+  pkg_llm_pi_ai --> pkg_authorization
+  pkg_llm_pi_ai --> pkg_credentials
+  pkg_llm_pi_ai --> pkg_fs
+  pkg_llm_pi_ai --> pkg_invariants
+  pkg_llm_pi_ai --> pkg_launch_environment
+  pkg_llm_pi_ai --> pkg_llm
+  pkg_llm_pi_ai --> pkg_settings
+  pkg_llm_pi_ai --> pkg_timeout
   pkg_llm_retry --> pkg_agent
   pkg_llm_retry --> pkg_brand
   pkg_llm_retry --> pkg_invariants
   pkg_llm_retry --> pkg_llm
   pkg_llm_retry --> pkg_session
+  pkg_llm_retry --> pkg_session_projection
   pkg_llm_retry --> pkg_timeout
   pkg_agent_default_model --> pkg_agent
   pkg_agent_default_model --> pkg_invariants
@@ -528,10 +598,14 @@ flowchart TD
   pkg_goal --> pkg_session
   pkg_goal --> pkg_session_projection
   pkg_goal --> pkg_typert_protocol
-  pkg_fs --> pkg_brand
-  pkg_fs --> pkg_invariants
-  pkg_fs --> pkg_llm
-  pkg_fs --> pkg_sandbox
+  pkg_fs_local --> pkg_fs
+  pkg_fs_local --> pkg_invariants
+  pkg_fs_observation_policy --> pkg_fs
+  pkg_fs_observation_policy --> pkg_invariants
+  pkg_skill_filesystem --> pkg_fs
+  pkg_skill_filesystem --> pkg_home_paths
+  pkg_skill_filesystem --> pkg_invariants
+  pkg_skill_filesystem --> pkg_skill
   pkg_web_search_deepseek --> pkg_agent
   pkg_web_search_deepseek --> pkg_credentials
   pkg_web_search_deepseek --> pkg_invariants
@@ -539,21 +613,24 @@ flowchart TD
   pkg_web_search_deepseek --> pkg_session
   pkg_web_search_deepseek --> pkg_settings
   pkg_web_search_deepseek --> pkg_web
-  pkg_spill_local --> pkg_invariants
-  pkg_spill_local --> pkg_spill
+  pkg_hook_protocol --> pkg_invariants
+  pkg_hook_protocol --> pkg_session
+  pkg_hook_protocol --> pkg_shell
   pkg_file_reference --> pkg_agent
   pkg_file_reference --> pkg_invariants
   pkg_time_context --> pkg_agent
   pkg_time_context --> pkg_invariants
   pkg_time_context --> pkg_llm
   pkg_time_context --> pkg_session
-  pkg_message_feedback --> pkg_brand
-  pkg_message_feedback --> pkg_invariants
-  pkg_message_feedback --> pkg_llm
-  pkg_message_feedback --> pkg_session
-  pkg_message_feedback --> pkg_session_persistence
-  pkg_message_feedback --> pkg_storage_domain
-  pkg_message_feedback --> pkg_typert_protocol
+  pkg_time_context --> pkg_session_projection
+  pkg_tmux_context --> pkg_agent
+  pkg_tmux_context --> pkg_invariants
+  pkg_tmux_context --> pkg_session
+  pkg_tmux_context --> pkg_session_projection
+  pkg_tmux_context --> pkg_shell
+  pkg_fs_e2b --> pkg_e2b
+  pkg_fs_e2b --> pkg_fs
+  pkg_fs_e2b --> pkg_invariants
   pkg_commands --> pkg_agent
   pkg_commands --> pkg_attachment
   pkg_commands --> pkg_brand
@@ -577,46 +654,38 @@ flowchart TD
   pkg_jobs --> pkg_brand
   pkg_jobs --> pkg_invariants
   pkg_jobs --> pkg_session
-  pkg_sandbox_local --> pkg_invariants
-  pkg_sandbox_local --> pkg_llm
-  pkg_sandbox_local --> pkg_sandbox
-  pkg_sandbox_local --> pkg_session
+  pkg_lsp_stdio --> pkg_brand
+  pkg_lsp_stdio --> pkg_fs
+  pkg_lsp_stdio --> pkg_invariants
+  pkg_lsp_stdio --> pkg_llm
+  pkg_lsp_stdio --> pkg_lsp
+  pkg_lsp_stdio --> pkg_subprocess
+  pkg_lsp_stdio --> pkg_timeout
   pkg_sandbox_policy --> pkg_agent
   pkg_sandbox_policy --> pkg_invariants
   pkg_sandbox_policy --> pkg_sandbox
   pkg_sandbox_policy --> pkg_session
+  pkg_sandbox_policy --> pkg_session_projection
   pkg_sandbox_policy --> pkg_system_prompt
-  pkg_session_persistence_jsonl --> pkg_invariants
-  pkg_session_persistence_jsonl --> pkg_session
-  pkg_session_persistence_jsonl --> pkg_session_persistence
-  pkg_session_persistence_sqlite --> pkg_invariants
-  pkg_session_persistence_sqlite --> pkg_llm
-  pkg_session_persistence_sqlite --> pkg_session
-  pkg_session_persistence_sqlite --> pkg_session_persistence
-  pkg_session_projection_cache --> pkg_invariants
-  pkg_session_projection_cache --> pkg_session
-  pkg_session_projection_cache --> pkg_session_projection
-  pkg_session_projection_cache --> pkg_storage_domain
-  pkg_session_stats --> pkg_invariants
-  pkg_session_stats --> pkg_llm
-  pkg_session_stats --> pkg_session
-  pkg_session_stats --> pkg_session_projection
   pkg_session_telemetry --> pkg_agent
   pkg_session_telemetry --> pkg_invariants
   pkg_session_telemetry --> pkg_session
+  pkg_session_title --> pkg_agent
   pkg_session_title --> pkg_brand
   pkg_session_title --> pkg_invariants
   pkg_session_title --> pkg_llm
   pkg_session_title --> pkg_session
   pkg_session_title --> pkg_session_projection
-  pkg_settings_file --> pkg_atomic_write
-  pkg_settings_file --> pkg_home_paths
-  pkg_settings_file --> pkg_invariants
-  pkg_settings_file --> pkg_settings
-  pkg_shell --> pkg_invariants
-  pkg_shell --> pkg_sandbox
-  pkg_shell --> pkg_settings
-  pkg_shell --> pkg_subprocess
+  pkg_bash_local --> pkg_invariants
+  pkg_bash_local --> pkg_settings
+  pkg_bash_local --> pkg_shell
+  pkg_bash_local --> pkg_subprocess
+  pkg_bash_local --> pkg_timeout
+  pkg_pwsh_local --> pkg_invariants
+  pkg_pwsh_local --> pkg_settings
+  pkg_pwsh_local --> pkg_shell
+  pkg_pwsh_local --> pkg_subprocess
+  pkg_pwsh_local --> pkg_timeout
   pkg_terminal --> pkg_agent
   pkg_terminal --> pkg_brand
   pkg_terminal --> pkg_invariants
@@ -629,34 +698,6 @@ flowchart TD
   pkg_workflow --> pkg_invariants
   pkg_workflow --> pkg_llm
   pkg_workflow --> pkg_session
-  pkg_workspace --> pkg_brand
-  pkg_workspace --> pkg_invariants
-  pkg_workspace --> pkg_session
-  pkg_workspace --> pkg_session_persistence
-  pkg_workspace --> pkg_storage
-  pkg_workspace --> pkg_storage_domain
-  pkg_llm_deepseek --> pkg_anonymous_user_id
-  pkg_llm_deepseek --> pkg_atomic_write
-  pkg_llm_deepseek --> pkg_attachment
-  pkg_llm_deepseek --> pkg_brand
-  pkg_llm_deepseek --> pkg_credentials
-  pkg_llm_deepseek --> pkg_deepseek_llm_api_extensions
-  pkg_llm_deepseek --> pkg_fs
-  pkg_llm_deepseek --> pkg_home_paths
-  pkg_llm_deepseek --> pkg_invariants
-  pkg_llm_deepseek --> pkg_launch_environment
-  pkg_llm_deepseek --> pkg_llm
-  pkg_llm_deepseek --> pkg_settings
-  pkg_llm_deepseek --> pkg_timeout
-  pkg_llm_pi_ai --> pkg_attachment
-  pkg_llm_pi_ai --> pkg_authorization
-  pkg_llm_pi_ai --> pkg_credentials
-  pkg_llm_pi_ai --> pkg_fs
-  pkg_llm_pi_ai --> pkg_invariants
-  pkg_llm_pi_ai --> pkg_launch_environment
-  pkg_llm_pi_ai --> pkg_llm
-  pkg_llm_pi_ai --> pkg_settings
-  pkg_llm_pi_ai --> pkg_timeout
   pkg_tools --> pkg_agent
   pkg_tools --> pkg_code_runtime
   pkg_tools --> pkg_invariants
@@ -674,17 +715,11 @@ flowchart TD
   pkg_goal_round_driver --> pkg_invariants
   pkg_goal_round_driver --> pkg_llm
   pkg_goal_round_driver --> pkg_session
-  pkg_fs_local --> pkg_fs
-  pkg_fs_local --> pkg_invariants
-  pkg_fs_observation_policy --> pkg_fs
-  pkg_fs_observation_policy --> pkg_invariants
-  pkg_skill_filesystem --> pkg_fs
-  pkg_skill_filesystem --> pkg_home_paths
-  pkg_skill_filesystem --> pkg_invariants
-  pkg_skill_filesystem --> pkg_skill
-  pkg_hook_protocol --> pkg_invariants
-  pkg_hook_protocol --> pkg_session
-  pkg_hook_protocol --> pkg_shell
+  pkg_fs_sandbox --> pkg_fs
+  pkg_fs_sandbox --> pkg_fs_local
+  pkg_fs_sandbox --> pkg_invariants
+  pkg_fs_sandbox --> pkg_sandbox
+  pkg_fs_sandbox --> pkg_sandbox_policy
   pkg_headless --> pkg_agent
   pkg_headless --> pkg_agent_default_model
   pkg_headless --> pkg_invariants
@@ -695,13 +730,6 @@ flowchart TD
   pkg_compaction --> pkg_invariants
   pkg_compaction --> pkg_llm
   pkg_compaction --> pkg_session
-  pkg_tmux_context --> pkg_agent
-  pkg_tmux_context --> pkg_invariants
-  pkg_tmux_context --> pkg_session
-  pkg_tmux_context --> pkg_shell
-  pkg_fs_e2b --> pkg_e2b
-  pkg_fs_e2b --> pkg_fs
-  pkg_fs_e2b --> pkg_invariants
   pkg_command_feedback --> pkg_anonymous_user_id
   pkg_command_feedback --> pkg_commands
   pkg_command_feedback --> pkg_invariants
@@ -721,33 +749,27 @@ flowchart TD
   pkg_jobs_local --> pkg_jobs
   pkg_jobs_local --> pkg_scope
   pkg_jobs_local --> pkg_timeout
-  pkg_lsp_stdio --> pkg_brand
-  pkg_lsp_stdio --> pkg_fs
-  pkg_lsp_stdio --> pkg_invariants
-  pkg_lsp_stdio --> pkg_llm
-  pkg_lsp_stdio --> pkg_lsp
-  pkg_lsp_stdio --> pkg_subprocess
-  pkg_lsp_stdio --> pkg_timeout
   pkg_session_title_llm --> pkg_invariants
   pkg_session_title_llm --> pkg_llm
   pkg_session_title_llm --> pkg_session
   pkg_session_title_llm --> pkg_session_title
   pkg_session_title_llm --> pkg_timeout
-  pkg_bash_local --> pkg_invariants
-  pkg_bash_local --> pkg_settings
-  pkg_bash_local --> pkg_shell
-  pkg_bash_local --> pkg_subprocess
-  pkg_bash_local --> pkg_timeout
-  pkg_pwsh_local --> pkg_invariants
-  pkg_pwsh_local --> pkg_settings
-  pkg_pwsh_local --> pkg_shell
-  pkg_pwsh_local --> pkg_subprocess
-  pkg_pwsh_local --> pkg_timeout
+  pkg_bash_sandbox --> pkg_bash_local
+  pkg_bash_sandbox --> pkg_invariants
+  pkg_bash_sandbox --> pkg_sandbox
+  pkg_bash_sandbox --> pkg_sandbox_policy
+  pkg_bash_sandbox --> pkg_shell
+  pkg_pwsh_sandbox --> pkg_invariants
+  pkg_pwsh_sandbox --> pkg_pwsh_local
+  pkg_pwsh_sandbox --> pkg_sandbox
+  pkg_pwsh_sandbox --> pkg_sandbox_policy
+  pkg_pwsh_sandbox --> pkg_shell
   pkg_terminal_bash --> pkg_agent
   pkg_terminal_bash --> pkg_invariants
   pkg_terminal_bash --> pkg_sandbox
   pkg_terminal_bash --> pkg_sandbox_policy
   pkg_terminal_bash --> pkg_session
+  pkg_terminal_bash --> pkg_session_projection
   pkg_terminal_bash --> pkg_subprocess
   pkg_terminal_bash --> pkg_terminal
   pkg_token_meter --> pkg_compaction
@@ -762,6 +784,7 @@ flowchart TD
   pkg_agent_loop --> pkg_scope
   pkg_agent_loop --> pkg_session
   pkg_agent_loop --> pkg_session_persistence
+  pkg_agent_loop --> pkg_session_projection
   pkg_agent_loop --> pkg_settings
   pkg_agent_loop --> pkg_system_prompt
   pkg_agent_loop --> pkg_tools
@@ -772,13 +795,9 @@ flowchart TD
   pkg_tool_goal --> pkg_invariants
   pkg_tool_goal --> pkg_llm
   pkg_tool_goal --> pkg_session
+  pkg_tool_goal --> pkg_session_projection
   pkg_tool_goal --> pkg_system_prompt
   pkg_tool_goal --> pkg_tools
-  pkg_fs_sandbox --> pkg_fs
-  pkg_fs_sandbox --> pkg_fs_local
-  pkg_fs_sandbox --> pkg_invariants
-  pkg_fs_sandbox --> pkg_sandbox
-  pkg_fs_sandbox --> pkg_sandbox_policy
   pkg_tool_fs --> pkg_attachment
   pkg_tool_fs --> pkg_fs
   pkg_tool_fs --> pkg_invariants
@@ -839,6 +858,7 @@ flowchart TD
   pkg_hooks_codex --> pkg_llm
   pkg_hooks_codex --> pkg_session
   pkg_hooks_codex --> pkg_session_persistence
+  pkg_hooks_codex --> pkg_session_projection
   pkg_hooks_codex --> pkg_tools
   pkg_command_compact --> pkg_commands
   pkg_command_compact --> pkg_compaction
@@ -849,6 +869,7 @@ flowchart TD
   pkg_agent_instructions --> pkg_invariants
   pkg_agent_instructions --> pkg_llm
   pkg_agent_instructions --> pkg_session
+  pkg_agent_instructions --> pkg_session_projection
   pkg_agent_instructions --> pkg_tools
   pkg_file_reference_local --> pkg_agent
   pkg_file_reference_local --> pkg_file_reference
@@ -934,16 +955,6 @@ flowchart TD
   pkg_session_title_first_prompt_llm --> pkg_session
   pkg_session_title_first_prompt_llm --> pkg_session_title
   pkg_session_title_first_prompt_llm --> pkg_session_title_llm
-  pkg_bash_sandbox --> pkg_bash_local
-  pkg_bash_sandbox --> pkg_invariants
-  pkg_bash_sandbox --> pkg_sandbox
-  pkg_bash_sandbox --> pkg_sandbox_policy
-  pkg_bash_sandbox --> pkg_shell
-  pkg_pwsh_sandbox --> pkg_invariants
-  pkg_pwsh_sandbox --> pkg_pwsh_local
-  pkg_pwsh_sandbox --> pkg_sandbox
-  pkg_pwsh_sandbox --> pkg_sandbox_policy
-  pkg_pwsh_sandbox --> pkg_shell
   pkg_shell_env --> pkg_home_paths
   pkg_shell_env --> pkg_invariants
   pkg_shell_env --> pkg_session_persistence
@@ -1097,9 +1108,11 @@ flowchart TD
   pkg_session_query_sqlite --> pkg_session
   pkg_session_query_sqlite --> pkg_session_persistence
   pkg_session_query_sqlite --> pkg_session_query
+  pkg_tool_session_query --> pkg_agent
   pkg_tool_session_query --> pkg_invariants
   pkg_tool_session_query --> pkg_llm
   pkg_tool_session_query --> pkg_session
+  pkg_tool_session_query --> pkg_session_projection
   pkg_tool_session_query --> pkg_session_query
   pkg_tool_session_query --> pkg_system_prompt
   pkg_tool_session_query --> pkg_timeout
@@ -1194,6 +1207,7 @@ flowchart TD
   pkg_tool_subagent --> pkg_llm
   pkg_tool_subagent --> pkg_scope
   pkg_tool_subagent --> pkg_session
+  pkg_tool_subagent --> pkg_session_projection
   pkg_tool_subagent --> pkg_settings
   pkg_tool_subagent --> pkg_subagent
   pkg_tool_subagent --> pkg_system_prompt
@@ -1214,6 +1228,7 @@ flowchart TD
   pkg_hooks_claude_code --> pkg_llm
   pkg_hooks_claude_code --> pkg_session
   pkg_hooks_claude_code --> pkg_session_persistence
+  pkg_hooks_claude_code --> pkg_session_projection
   pkg_hooks_claude_code --> pkg_subagent
   pkg_hooks_claude_code --> pkg_tools
   pkg_api_session_controller --> pkg_agent
@@ -1255,6 +1270,7 @@ flowchart TD
   pkg_experimental_agent_team --> pkg_llm
   pkg_experimental_agent_team --> pkg_session
   pkg_experimental_agent_team --> pkg_session_persistence
+  pkg_experimental_agent_team --> pkg_session_projection
   pkg_experimental_agent_team --> pkg_subagent
   pkg_experimental_agent_team --> pkg_typert_protocol
   pkg_sdk_protocol --> pkg_invariants
@@ -1710,7 +1726,7 @@ flowchart TD
   pkg_client_ui_cordis --> pkg_invariants
 ```
 
-| 包 | 分组 | 依赖 |
+| Package | Group | Depends on |
 | --- | --- | --- |
 | [`invariants`](../packages/runtime-diagnostics/invariants) | `runtime-diagnostics` | — |
 | [`atomic-write`](../packages/util/atomic-write) | `util` | [`invariants`](../packages/runtime-diagnostics/invariants) |
@@ -1774,7 +1790,6 @@ flowchart TD
 | [`web`](../packages/web/web) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`authorization`](../packages/credentials/authorization) | `credentials` | [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
 | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`skill-badge`](../packages/skill/skill-badge) | `skill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
 | [`web-fetch-http`](../packages/web/web-fetch-http) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`timeout`](../packages/util/timeout), [`web`](../packages/web/web) |
 | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`web`](../packages/web/web) |
@@ -1789,59 +1804,62 @@ flowchart TD
 | [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`session-snapshot`](../packages/test-support/session-snapshot) | `test-support` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
-| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
-| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
+| [`agent`](../packages/core/agent) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
-| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
 | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`spill`](../packages/spill/spill) |
-| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`message-feedback`](../packages/feedback/message-feedback) | `feedback` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol) |
-| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |
-| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
-| [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
-| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
 | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
-| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
 | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
 | [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain) |
 | [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
-| [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
-| [`session-title`](../packages/session/session-title) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
 | [`settings-file`](../packages/settings/settings-file) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings) |
 | [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
-| [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
-| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) |
 | [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`atomic-write`](../packages/util/atomic-write), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`credentials`](../packages/credentials/credentials), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
 | [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`attachment`](../packages/attachment/attachment), [`authorization`](../packages/credentials/authorization), [`credentials`](../packages/credentials/credentials), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings), [`timeout`](../packages/util/timeout) |
-| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
-| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
-| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`timeout`](../packages/util/timeout) |
+| [`agent-default-model`](../packages/core/agent-default-model) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`settings`](../packages/settings/settings) |
+| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`typert-protocol`](../packages/typert/protocol) |
 | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`fs-observation-policy`](../packages/fs/fs-observation-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`skill-filesystem`](../packages/skill/skill-filesystem) | `skill` | [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`skill`](../packages/skill/skill) |
+| [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/runtime-diagnostics/invariants), [`launch-environment`](../packages/util/launch-environment), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`web`](../packages/web/web) |
 | [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
+| [`file-reference`](../packages/context/file-reference) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
+| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`shell`](../packages/shell/shell) |
+| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`typert-protocol`](../packages/typert/protocol) |
+| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
+| [`user-questions`](../packages/interaction/user-questions) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
+| [`jobs`](../packages/jobs/jobs) | `jobs` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt) |
+| [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session) |
+| [`session-title`](../packages/session/session-title) | `session` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
+| [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
+| [`terminal`](../packages/terminal/terminal) | `terminal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants) |
+| [`loader-smoke`](../packages/test-support/loader-smoke) | `test-support` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) |
+| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm) |
+| [`goal-round-driver`](../packages/goal/goal-round-driver) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
+| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
 | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
 | [`compaction`](../packages/compaction/compaction) | `compaction` | [`brand`](../packages/util/brand), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
-| [`tmux-context`](../packages/context/tmux-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`shell`](../packages/shell/shell) |
-| [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants) |
 | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`permission-presets`](../packages/interaction/permission-presets) | `interaction` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`user-approval`](../packages/interaction/user-approval) |
 | [`jobs-local`](../packages/jobs/jobs-local) | `jobs` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`scope`](../packages/core/scope), [`timeout`](../packages/util/timeout) |
-| [`lsp-stdio`](../packages/lsp/lsp-stdio) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) |
-| [`bash-local`](../packages/shell/bash-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
-| [`pwsh-local`](../packages/shell/pwsh-local) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`settings`](../packages/settings/settings), [`shell`](../packages/shell/shell), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
-| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
+| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
+| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
+| [`terminal-bash`](../packages/terminal/terminal-bash) | `terminal` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subprocess`](../packages/subprocess/subprocess), [`terminal`](../packages/terminal/terminal) |
 | [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
-| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
+| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`agent-tool-presentation`](../packages/core/agent-tool-presentation) | `core` | [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
-| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) |
+| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
 | [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) |
@@ -1850,9 +1868,9 @@ flowchart TD
 | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) |
 | [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-questions`](../packages/interaction/user-questions) |
-| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
+| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`command-compact`](../packages/compaction/command-compact) | `compaction` | [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants) |
-| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
+| [`agent-instructions`](../packages/context/agent-instructions) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
 | [`file-reference-local`](../packages/context/file-reference-local) | `context` | [`agent`](../packages/core/agent), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`cordis-host-runner`](../packages/extensions/cordis-host-runner) | `extensions` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
 | [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`tools`](../packages/core/tools) |
@@ -1867,8 +1885,6 @@ flowchart TD
 | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
 | [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
 | [`session-title-first-prompt-llm`](../packages/session/session-title-first-prompt-llm) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
-| [`bash-sandbox`](../packages/shell/bash-sandbox) | `shell` | [`bash-local`](../packages/shell/bash-local), [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
-| [`pwsh-sandbox`](../packages/shell/pwsh-sandbox) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`pwsh-local`](../packages/shell/pwsh-local), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell) |
 | [`shell-env`](../packages/shell/shell-env) | `shell` | [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`session-persistence`](../packages/session/session-persistence), [`shell`](../packages/shell/shell), [`tools`](../packages/core/tools) |
 | [`tool-bash-persistent`](../packages/shell/tool-bash-persistent) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`tool-pwsh-persistent`](../packages/shell/tool-pwsh-persistent) | `shell` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`terminal`](../packages/terminal/terminal), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
@@ -1889,7 +1905,7 @@ flowchart TD
 | [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
 | [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
 | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
-| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
+| [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
 | [`api-gateway`](../packages/api/gateway) | `api` | [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/runtime-diagnostics/invariants), [`typert-registry`](../packages/typert/registry) |
 | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
 | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`typert-protocol`](../packages/typert/protocol) |
@@ -1901,13 +1917,13 @@ flowchart TD
 | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
 | [`subagent-in-process-driver`](../packages/subagent/subagent-in-process-driver) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
+| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
 | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
 | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
-| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
+| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
 | [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`brand`](../packages/util/brand), [`client-connection`](../packages/client/connection), [`file-reference`](../packages/context/file-reference), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
 | [`api-workspace-controller`](../packages/api/workspace-controller) | `api` | [`api-gateway`](../packages/api/gateway), [`client-connection`](../packages/client/connection), [`host-directory-picker`](../packages/host/directory-picker), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`storage-domain`](../packages/storage/storage-domain), [`typert-protocol`](../packages/typert/protocol), [`workspace`](../packages/workspace/workspace) |
-| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
+| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
 | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) |
 | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
 | [`workflow-worker-thread`](../packages/workflow/workflow-worker-thread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |

+ 2 - 2
docs/persistence-catalog.i18n.yaml

@@ -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 docs/persistence-catalog.md
-persistence-catalog.md: 2143f7881edad36a986869fcbb458ae464014a44
-persistence-catalog.zh.md: abe6511a8ad9edbbc0e73ab77e02cf10d6346364
+persistence-catalog.md: b3c7ee3d13d1098d560a99554491a9890a4083b5
+persistence-catalog.zh.md: 730d4b0db3752979955ecedfa94985c0f03066bd

+ 8 - 8
docs/persistence-catalog.md

@@ -104,7 +104,7 @@ Sources: [`packages/core/session/src/types.ts:328`](../packages/core/session/src
 }
 ```
 
-Source: [`packages/core/agent/src/types.ts:38`](../packages/core/agent/src/types.ts)
+Source: [`packages/core/agent/src/types.ts:58`](../packages/core/agent/src/types.ts)
 
 ### `agent-preset/*`
 
@@ -513,13 +513,13 @@ Source: [`packages/api/session-controller/src/types.ts:40`](../packages/api/sess
 /**
  * Records the selected preset as durable, log-only user intent. The knob
  * events follow in the same turn and control execution; this event stays
- * out of the model transcript and lets {@link effectivePermissionPreset}
+ * out of the model transcript and lets the permission projection unit
  * preserve a selection when bundles match.
  */
 'permission/preset': { preset: string }
 ```
 
-Source: [`packages/interaction/permission-presets/src/index.ts:50`](../packages/interaction/permission-presets/src/index.ts)
+Source: [`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts)
 
 ### `plan/*`
 
@@ -531,12 +531,12 @@ Source: [`packages/interaction/permission-presets/src/index.ts:50`](../packages/
 /**
  * Whether plan mode is in force from this point on: log-only, non-surface,
  * whole-value replace. The last `plan/mode` wins; a log with none folds to
- * inactive through {@link foldPlanMode}.
+ * inactive through the projection unit's fold.
  */
 'plan/mode': { active: boolean }
 ```
 
-Source: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts)
+Source: [`packages/plan/plan-mode/src/index.ts:47`](../packages/plan/plan-mode/src/index.ts)
 
 ### `request/*`
 
@@ -584,7 +584,7 @@ Source: [`packages/core/session/src/types.ts:291`](../packages/core/session/src/
  * The session's sandbox mode was switched — log-only (like `approval/*`;
  * NOT a surface event, carries no `surfaceOp`): durable and replayable,
  * never in the model transcript. The LAST such event is the session's
- * override ({@link effectiveSandboxMode}). `source: 'delegation'` marks
+ * override (folded by the sandboxMode projection unit). `source: 'delegation'` marks
  * an override seeded into a child; an absent source is a runtime switch.
  */
 'sandbox/mode': {
@@ -662,7 +662,7 @@ Source: [`packages/core/session/src/types.ts:324`](../packages/core/session/src/
 
 Types: [SessionTitleEventData](subsystems/session-title.md)
 
-Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts)
+Source: [`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
 
 <a id="sessiontitle-llm-request--log-only"></a>
 
@@ -755,7 +755,7 @@ Source: [`packages/subagent/subagent/src/descriptor.ts:38`](../packages/subagent
 }
 ```
 
-Source: [`packages/subagent/tool-subagent/src/model-selection-state.ts:14`](../packages/subagent/tool-subagent/src/model-selection-state.ts)
+Source: [`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../packages/subagent/tool-subagent/src/model-selection-state.ts)
 
 ### `team/*`
 

+ 8 - 8
docs/persistence-catalog.zh.md

@@ -106,7 +106,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 }
 ```
 
-来源:[`packages/core/agent/src/types.ts:38`](../packages/core/agent/src/types.ts)
+来源:[`packages/core/agent/src/types.ts:58`](../packages/core/agent/src/types.ts)
 
 ### `agent-preset/*`
 
@@ -515,13 +515,13 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 /**
  * Records the selected preset as durable, log-only user intent. The knob
  * events follow in the same turn and control execution; this event stays
- * out of the model transcript and lets {@link effectivePermissionPreset}
+ * out of the model transcript and lets the permission projection unit
  * preserve a selection when bundles match.
  */
 'permission/preset': { preset: string }
 ```
 
-来源:[`packages/interaction/permission-presets/src/index.ts:50`](../packages/interaction/permission-presets/src/index.ts)
+来源:[`packages/interaction/permission-presets/src/index.ts:53`](../packages/interaction/permission-presets/src/index.ts)
 
 ### `plan/*`
 
@@ -533,12 +533,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 /**
  * Whether plan mode is in force from this point on: log-only, non-surface,
  * whole-value replace. The last `plan/mode` wins; a log with none folds to
- * inactive through {@link foldPlanMode}.
+ * inactive through the projection unit's fold.
  */
 'plan/mode': { active: boolean }
 ```
 
-来源:[`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/src/index.ts)
+来源:[`packages/plan/plan-mode/src/index.ts:47`](../packages/plan/plan-mode/src/index.ts)
 
 ### `request/*`
 
@@ -586,7 +586,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
  * The session's sandbox mode was switched — log-only (like `approval/*`;
  * NOT a surface event, carries no `surfaceOp`): durable and replayable,
  * never in the model transcript. The LAST such event is the session's
- * override ({@link effectiveSandboxMode}). `source: 'delegation'` marks
+ * override (folded by the sandboxMode projection unit). `source: 'delegation'` marks
  * an override seeded into a child; an absent source is a runtime switch.
  */
 'sandbox/mode': {
@@ -664,7 +664,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 
 类型:[SessionTitleEventData](subsystems/session-title.zh.md)
 
-来源:[`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts)
+来源:[`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
 
 <a id="sessiontitle-llm-request--log-only"></a>
 
@@ -757,7 +757,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 }
 ```
 
-来源:[`packages/subagent/tool-subagent/src/model-selection-state.ts:14`](../packages/subagent/tool-subagent/src/model-selection-state.ts)
+来源:[`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../packages/subagent/tool-subagent/src/model-selection-state.ts)
 
 ### `team/*`
 

+ 2 - 2
docs/subsystems/goal.i18n.yaml

@@ -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 docs/subsystems/goal.md
-goal.md: 858d0f073f570185ff101f0d64ac701693e5e866
-goal.zh.md: 4e3687c3dddc4fda738f71fec8ded39d3b7c3e92
+goal.md: 2fffebffa97221f0234750402bc6cb6361facec8
+goal.zh.md: f3d4b41204a303a49f059056864e2e56b1786475

+ 1 - 1
docs/subsystems/goal.md

@@ -142,7 +142,7 @@ interface GoalChanged {
 
 ## Service behavior
 
-[`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay from durable `goal/change` events, enforces exact-live-agent identity and compare-and-set mutations, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) defines the callable API and model-visible contract.
+[`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, reads strict replay from the optionally registered `goal` projection, enforces exact-live-agent identity and compare-and-set mutations, and emits contained `goal/changed` notifications. Its first dependent access fails if the projection registry or key is absent. The package [README](../../packages/goal/goal/README.md) defines the callable API and model-visible contract.
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 

+ 1 - 1
docs/subsystems/goal.zh.md

@@ -142,7 +142,7 @@ interface GoalChanged {
 
 ## 服务行为
 
-[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 `goal/change` 事件执行严格回放折叠、校验传入的 agent(智能体)是注册表中的确切活跃实例、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.zh.md) 定义可调用 API 和面向模型的约定。
+[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从可选注册的 `goal` 投影读取严格回放结果、校验传入的 agent(智能体)是注册表中的确切活跃实例、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。注册表或 key 缺失时,第一次依赖它们的访问会失败。包 [README](../../packages/goal/goal/README.zh.md) 定义可调用 API 和面向模型的约定。
 
 <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
 

+ 2 - 2
docs/subsystems/permission-presets.i18n.yaml

@@ -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 docs/subsystems/permission-presets.md
-permission-presets.md: ccbf1a0133571e56b780aadf9f08150d291fda35
-permission-presets.zh.md: 3ec3fde572da32c3276cfcea2db2b256f55c845c
+permission-presets.md: f4c3fafb9eea79f58255f609affaf906c24c3bcd
+permission-presets.zh.md: 7dd2927ad2d17c1715ad13085c791a73b5963c6a

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است