Jelajahi Sumber

Merge remote-tracking branch 'origin/master' into xtr/explicit-agent-context

_Kerman 2 minggu lalu
induk
melakukan
46d01068c6
100 mengubah file dengan 605 tambahan dan 206 penghapusan
  1. 2 2
      .agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.i18n.yaml
  2. 3 3
      .agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.md
  3. 3 3
      .agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.zh.md
  4. 6 0
      .agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.i18n.yaml
  5. 33 0
      .agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.md
  6. 33 0
      .agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.zh.md
  7. 6 0
      .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.i18n.yaml
  8. 74 0
      .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md
  9. 74 0
      .agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md
  10. 2 2
      .agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml
  11. 3 3
      .agents/notes/implemented/feature/2026-08-05-agent-teams.md
  12. 3 3
      .agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md
  13. 2 2
      .agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.i18n.yaml
  14. 8 6
      .agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.md
  15. 8 6
      .agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.zh.md
  16. 6 0
      .agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.i18n.yaml
  17. 41 0
      .agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.md
  18. 41 0
      .agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.zh.md
  19. 2 2
      .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml
  20. 0 1
      .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md
  21. 1 1
      .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md
  22. 1 1
      .github/issue-management/config.json
  23. 5 2
      .github/issue-management/policy.mjs
  24. 31 9
      .github/issue-management/policy.test.mjs
  25. 1 1
      apps/cli/package.json
  26. 6 0
      apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs
  27. 55 0
      apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts
  28. 1 1
      apps/web/package.json
  29. 2 2
      docs/config-catalog.i18n.yaml
  30. 1 1
      docs/config-catalog.md
  31. 1 1
      docs/config-catalog.zh.md
  32. 2 2
      docs/persistence-catalog.i18n.yaml
  33. 8 8
      docs/persistence-catalog.md
  34. 8 8
      docs/persistence-catalog.zh.md
  35. 2 2
      docs/subsystems/agent-team.i18n.yaml
  36. 4 3
      docs/subsystems/agent-team.md
  37. 4 3
      docs/subsystems/agent-team.zh.md
  38. 2 2
      docs/subsystems/storage.i18n.yaml
  39. 28 5
      docs/subsystems/storage.md
  40. 28 5
      docs/subsystems/storage.zh.md
  41. 2 2
      docs/tool-catalog.i18n.yaml
  42. 3 29
      docs/tool-catalog.md
  43. 2 28
      docs/tool-catalog.zh.md
  44. 1 1
      package.json
  45. 1 1
      packages/acp/acp/package.json
  46. 1 1
      packages/api/gateway/package.json
  47. 1 1
      packages/api/remotes/package.json
  48. 1 1
      packages/api/session-controller/package.json
  49. 1 1
      packages/api/settings-controller/package.json
  50. 1 1
      packages/api/workspace-controller/package.json
  51. 1 1
      packages/attachment/attachment-local/package.json
  52. 1 1
      packages/attachment/attachment/package.json
  53. 1 1
      packages/boot/app-boot/package.json
  54. 1 1
      packages/boot/cmdline/package.json
  55. 1 1
      packages/bundle/acp-app/package.json
  56. 1 1
      packages/bundle/base/package.json
  57. 1 1
      packages/bundle/headless/package.json
  58. 1 1
      packages/bundle/sdk-app/package.json
  59. 1 1
      packages/bundle/sdk-minimal/package.json
  60. 1 1
      packages/bundle/web-app/package.json
  61. 1 1
      packages/client/connection/package.json
  62. 1 1
      packages/client/hmr/package.json
  63. 1 1
      packages/client/locale/package.json
  64. 1 1
      packages/client/modules/package.json
  65. 1 1
      packages/client/store/package.json
  66. 1 1
      packages/client/ui-agent-preset/package.json
  67. 1 1
      packages/client/ui-approval/package.json
  68. 1 1
      packages/client/ui-attachment/package.json
  69. 1 1
      packages/client/ui-brand-official/package.json
  70. 1 1
      packages/client/ui-chat/package.json
  71. 1 1
      packages/client/ui-commands/package.json
  72. 1 1
      packages/client/ui-conversation/package.json
  73. 1 1
      packages/client/ui-deliverables/package.json
  74. 1 1
      packages/client/ui-directory-picker-browse/package.json
  75. 1 1
      packages/client/ui-directory-picker-native/package.json
  76. 1 1
      packages/client/ui-goal/package.json
  77. 1 1
      packages/client/ui-input-trigger/package.json
  78. 1 1
      packages/client/ui-jobs/package.json
  79. 1 1
      packages/client/ui-layout/package.json
  80. 1 1
      packages/client/ui-message-feedback/package.json
  81. 1 1
      packages/client/ui-model-selection/package.json
  82. 1 1
      packages/client/ui-permission-presets/package.json
  83. 1 1
      packages/client/ui-plan/package.json
  84. 1 1
      packages/client/ui-primitives/package.json
  85. 1 1
      packages/client/ui-reference/package.json
  86. 1 1
      packages/client/ui-renderer/package.json
  87. 1 1
      packages/client/ui-schedule/package.json
  88. 1 1
      packages/client/ui-session/package.json
  89. 1 1
      packages/client/ui-settings-general/package.json
  90. 1 1
      packages/client/ui-settings-models/package.json
  91. 1 1
      packages/client/ui-settings-plugin-inventory/package.json
  92. 1 1
      packages/client/ui-settings-plugins/package.json
  93. 1 1
      packages/client/ui-settings/package.json
  94. 1 1
      packages/client/ui-sidebar/package.json
  95. 1 1
      packages/client/ui-skill/package.json
  96. 1 1
      packages/client/ui-slots/package.json
  97. 1 1
      packages/client/ui-subagent/package.json
  98. 1 1
      packages/client/ui-theme/package.json
  99. 1 1
      packages/client/ui-tool/package.json
  100. 1 1
      packages/client/ui-trajectory/package.json

+ 2 - 2
.agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-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/architecture/2026-08-19-projection-cache-per-session-files.md
-2026-08-19-projection-cache-per-session-files.md: 0fd171649c1d8c7c3be7a8089287d43782b84714
-2026-08-19-projection-cache-per-session-files.zh.md: 47decb598cc70233c287feab2dddff46bd7bcbc9
+2026-08-19-projection-cache-per-session-files.md: c0ead55557e45905cfd368c70d826bf0c3eaade3
+2026-08-19-projection-cache-per-session-files.zh.md: e5582d14d68c1aed76ef8aab08a8d575e8a1842d

+ 3 - 3
.agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.md

@@ -20,8 +20,8 @@ Reads and writes share ONE coherent state: every read (`cachedSnapshot`) is a sy
 - Listing is a synchronous in-memory read; a session without a record document simply lacks the projection column.
 - ACP, headless, SDK, and Web sessions publish cache rows for later consumers. The log-leading durability barrier may flush a covered prefix at the cache cadence and split otherwise coalesced physical JSONL runs; recorded profile snapshots re-pack the logical event stream so cache timing does not define fixture layout.
 - The per-record contract scopes failure: a malformed or stale-version document reads as an absent record at open, so one bad file never bricks the cache, and a checkpoint schema bump discards stale sessions per record instead of rejecting the whole domain.
-- The json backend bootstraps the per-record tree from the legacy whole-unit cache only when enumeration finds no new-layout document path and the legacy unit name and version match the requested descriptor. A different version remains untouched and the new domain opens empty; storage never relabels its values as the current version. Any new document path, including an unreadable or stale file, suppresses the bootstrap for the whole unit; missing session rows refold from the log.
-- The `session_projcache` domain uses version 6. Every version-5 record reads as absent, including a healthy one, so a poisoned version-5 record cannot fail domain validation. Session headers and event logs remain in session persistence; an exact read refolds them and writes a version-6 cache record, while zero-I/O listings lack that projection until the cache returns.
+- The json backend bootstraps the per-record tree from the legacy whole-unit cache only when enumeration finds no new-layout document path, the legacy unit name matches, and its version is current or declared compatible. A version outside that accepted set remains untouched and the new domain opens empty; storage never relabels a version the domain owner did not approve. Any new document path, including an unreadable or stale file, suppresses the bootstrap for the whole unit; missing session rows refold from the log. The [cross-version read-compatibility decision](2026-09-02-projcache-cross-version-read-compat.md) owns the version policy.
+- The `session_projcache` domain uses version 6 and declares versions 3, 4, and 5 compatible. Vouched-for records retain their listing projections across upgrades, absent lineage fields normalize to an unseeded identity, and a seeded caller rejects that identity and refolds cold. A record that still fails schema validation is backed up and skipped; every subsequent write stamps version 6.
 - The cache record is bound to the same log lifecycle as before: the stored `{createdAt, cwd, isSeeded, inheritedEventCount}` identity guards against a recreated id or a mismatched inherited prefix.
 
 ## Alternatives considered
@@ -30,4 +30,4 @@ Reads and writes share ONE coherent state: every read (`cachedSnapshot`) is a sy
 - **Cache-owned per-session files** (`<root>/<session-id>/projection_cache.json`, the first revision of this change). Tried and reverted in review: the cache hand-rolled the medium — paths, per-path write chains, in-flight tracking, owner-only file modes, and a sqlite no-path special case — and its listing read hit the disk directly on every call while writes were throttled, so reads and writes were never consistent.
 - **Resolve the path through `sessionPersistence.locate(meta)`** (the file beside the session log). Rejected: the cache would have to guess "beside the log" from a log artifact path (`dirname` + fixed filename), coupling the cache to the persistence service and to a backend's layout.
 - **Make `per-record` a mode of the existing unit instead of a separate unit class.** Rejected: the two layouts have genuinely different state models — `single` is memory-authoritative with whole-file publish, `per-record` is stateless (the directory is the state; `loadAll` re-reads the tree) — so they are separate small classes behind one backend, with record keys validated path-safe instead of encoded.
-- **Copy legacy values across unit versions.** Rejected: the json backend does not know a domain's record schema and cannot derive session-lineage fields. Copying raw values under the requested version relabels data without migrating it. A domain that requires compatibility owns an explicit migration; the projection cache instead discards old records and rebuilds them from session logs.
+- **Copy legacy values across undeclared unit versions.** Rejected: the json backend does not know a domain's record schema and cannot derive session-lineage fields. It copies an older record only when the domain explicitly lists that version in `compatibleVersions` and its current schema accepts the value; otherwise the record stays untouched and reads as absent.

+ 3 - 3
.agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.zh.md

@@ -20,8 +20,8 @@ Status: implemented
 - 列表读取是同步内存读;没有记录文档的会话只是缺少投影列。
 - ACP、headless、SDK 与 Web 会话都会发布缓存行,供后续消费方使用。确保日志领先的持久性屏障可能按缓存节奏 flush 已覆盖的前缀,并拆分原本会合并的物理 JSONL 行;各 profile 的录制快照会重新 pack 逻辑事件流,因此缓存时序不会决定 fixture 布局。
 - per-record 契约把故障范围缩小到单记录:畸形或过期版本的文档在打开时读作"无此记录",单个坏文件不会拖垮整个缓存;检查点 schema 升级按会话丢弃过期行,而不是拒绝整个域。
-- json 后端仅在枚举时没有发现任何新布局文档路径,且旧单元名称和版本与请求的 descriptor 相同时,才从旧整单元缓存引导 per-record 目录树。版本不同时,旧文件保持不变,新域为空;存储不会把旧值改标为当前版本。只要存在任意新文档路径,即使文件不可读或版本陈旧,也会对整个单元禁用引导;缺失的会话行从日志重折叠。
-- `session_projcache` 域使用版本 6。所有版本 5 记录都读作缺失,包括健康记录,因此被污染的版本 5 记录不能再使域校验失败。会话 header 和事件日志仍保存在会话持久化中;精确读取会重折叠这些数据并写入版本 6 缓存,而零 I/O 列表在缓存恢复前缺少对应投影
+- json 后端仅在枚举时没有发现任何新布局文档路径、旧单元名称匹配,且其版本为当前版本或已声明兼容版本时,才从旧整单元缓存引导 per-record 目录树。接受集合之外的版本保持不变,新域为空;存储绝不把域 owner 未批准的版本改标为当前版本。只要存在任意新文档路径,即使文件不可读或版本陈旧,也会对整个单元禁用引导;缺失的会话行从日志重折叠。[跨版本读兼容决策](2026-09-02-projcache-cross-version-read-compat.zh.md)是版本策略的权威说明。
+- `session_projcache` 域使用版本 6,并声明版本 3、4、5 兼容。经背书的记录在升级后保留列表投影;缺失的 lineage 字段归一化为 unseeded 身份,seeded 调用方会拒绝该身份并回落冷折叠。仍然通不过 schema 校验的记录会被备份并跳过;后续每次写入都使用版本 6
 - 缓存记录仍绑定同一日志生命周期:存储的 `{createdAt, cwd, isSeeded, inheritedEventCount}` 身份防止被重建的 id 或不匹配的继承前缀误导。
 
 ## Alternatives considered
@@ -30,4 +30,4 @@ Status: implemented
 - **缓存自持的每会话文件**(`<root>/<session-id>/projection_cache.json`,本改动的第一版)。试过并在评审中回退:缓存手搓了介质——路径、按路径的写链、在途跟踪、仅属主文件权限,以及 sqlite 无路径特判——而且它的列表读每次调用都直读磁盘、写却在节流,读写永不一致。
 - **经 `sessionPersistence.locate(meta)` 解析路径**(文件放在会话日志旁)。未采用:缓存得从日志 artifact 路径"猜"日志旁边(`dirname` + 固定文件名),把缓存耦合到持久化服务与后端的布局。
 - **把 `per-record` 做成既有单元的一种模式而非独立单元类。** 未采用:两种布局的状态模型本质不同——`single` 内存权威、整文件发布;`per-record` 无状态(目录即状态,`loadAll` 重扫目录树)——所以它们是同一后端下的两个小型独立类,记录键做路径安全校验而非编码。
-- **跨单元版本复制旧值。** 未采用:json 后端不知道域的记录 schema,也无法推导会话谱系字段。按请求版本复制原始值只会修改数据标签,不会迁移数据。需要兼容性的域负责显式迁移;投影缓存改为丢弃旧记录,并从会话日志重建
+- **跨未声明单元版本复制旧值。** 未采用:json 后端不知道域的记录 schema,也无法推导会话 lineage 字段。只有当域在 `compatibleVersions` 中明确列出旧版本,且当前 schema 接受该值时,后端才复制旧记录;否则记录保持不变并读作不存在

+ 6 - 0
.agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.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-09-01-streamed-tool-call-identity.md
+2026-09-01-streamed-tool-call-identity.md: c52f39b735199270d65ed30a388333a217003237
+2026-09-01-streamed-tool-call-identity.zh.md: 9a7ffb6343870a08e06507408e007ad94fbad178

+ 33 - 0
.agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.md

@@ -0,0 +1,33 @@
+# Agent Note: Streamed tool-call identity survives empty continuation deltas
+
+Status: implemented
+
+English | [中文](2026-09-01-streamed-tool-call-identity.zh.md)
+
+## Problem
+
+The DeepSeek SSE translator assigned `id` and `name` on every tool-call delta that carried the field, so a continuation delta repeating either as an empty string erased the identity established by the call's first delta. The assembled block reached the loop with an empty name, which the tool registry refuses as `unknown tool ""`, leaving the affected models unable to run any tool. Gateways that fill those fields with `null` erased the identity the same way, and `WireToolCallDelta` declared both as `string | undefined`, keeping the observed `null` out of the compiler's reach.
+
+The empty identity outlived the turn. `appendToolCall` and `appendToolResult` write the block's id verbatim and no write path validates it, while `adoptSessionEvent` refuses a `tool/result` whose `callId` is empty, so the persistence coordinator wrapped that refusal in `SessionPersistenceCorruptionError`. A session that recorded one such call was writable and no longer loadable.
+
+## Decision
+
+`acceptIdentity` accepts only a non-empty string for a tool call's `id` and `name`; `undefined`, `null`, `''`, and any non-string leave the established value in place. The assignment set only narrows, so no input reaches a worse outcome than before. `WireToolCallDelta` widens `id`, `function.name`, and `function.arguments` to admit `null`, putting the values gateways actually send into the type system and making the runtime guard load-bearing rather than speculative.
+
+## Alternatives considered
+
+**Concatenate `id` and `name` across deltas.** Rejected: they are identity, not accumulation. Concatenation produces `Globnull` against a gateway that sends `null`, and a doubled name against one that repeats a non-empty value.
+
+**Refuse a conflicting non-empty identity mid-stream.** Deferred: a gateway that fragments a long tool name would be refused for it, and no observed provider re-sends a different non-empty identity within one call index.
+
+**Refuse a response whose tool call never receives an identity.** Deferred. It requires a new failure code, a change to the default retryable set, and a `[DONE]` gate that must not override the finish reason a provider already sent — cost and risk that the reported defect does not carry. The lenient wire it guards against is hypothetical: no report describes a stream that omits identity entirely.
+
+**Relax the session reader's empty-`callId` refusal.** Rejected: an empty `callId` cannot be paired back to the provider on the next request, so accepting it moves the failure into the model request. That refusal is the durable-boundary gate; the producer was the defect.
+
+## Consequences
+
+A continuation delta repeating identity empty or null is inert, so a call keeps the identity its first delta established, and the reported path to `unknown tool ""` and an unreadable session is closed. A stream that never carries identity at all still assembles an empty one, exactly as before; that path and the recovery of sessions already holding an empty `callId` are outside this change.
+
+## Testing
+
+`translate.spec.ts` covers empty and null continuation deltas, a repeated identical identity, and parallel calls holding separate identities under empty continuations. The existing cases for a wire that omits identity entirely keep their recorded empty-identity output.

+ 33 - 0
.agents/notes/implemented/architecture/2026-09-01-streamed-tool-call-identity.zh.md

@@ -0,0 +1,33 @@
+# Agent Note:流式工具调用身份不被空续传分片抹除
+
+Status: implemented
+
+[English](2026-09-01-streamed-tool-call-identity.md) | 中文
+
+## 问题
+
+DeepSeek SSE 翻译器对每个携带该字段的工具调用分片都直接赋值 `id` 与 `name`,因此续传分片把其中任一字段重复发送为空串时,会抹掉该调用首个分片已建立的身份。组装出的块带着空名字进入循环,工具注册表以 `unknown tool ""` 拒绝它,受影响的模型上任何工具都跑不起来。把这些字段填成 `null` 的网关会造成同样的抹除,而 `WireToolCallDelta` 把两者都声明为 `string | undefined`,让实际观察到的 `null` 落在编译器视野之外。
+
+空身份还会活过本轮。`appendToolCall` 与 `appendToolResult` 原样写入块的 id 且没有任何写入路径校验它,而 `adoptSessionEvent` 拒绝 `callId` 为空的 `tool/result`,持久化协调器于是把该拒绝包装成 `SessionPersistenceCorruptionError`。记录过一次这种调用的会话可写但不再可读。
+
+## 决定
+
+`acceptIdentity` 对工具调用的 `id` 与 `name` 只接受非空字符串;`undefined`、`null`、`''` 以及任何非字符串都保留已建立的值。会触发赋值的输入集合只减不增,因此没有任何输入会比改动前更差。`WireToolCallDelta` 把 `id`、`function.name` 与 `function.arguments` 放宽到允许 `null`,使网关实际发送的值进入类型系统,运行时守卫因此是承重的而非臆测的。
+
+## 考虑过的替代方案
+
+**跨分片拼接 `id` 与 `name`。** 否决:它们是身份而非累积。面对发送 `null` 的网关,拼接产生 `Globnull`;面对重复发送非空值的网关,产生重复的名字。
+
+**流中途拒绝冲突的非空身份。** 推迟:分片发送长工具名的网关会因此被拒,且没有观察到任何提供方在同一个调用 index 内改发不同的非空身份。
+
+**拒绝始终未获得身份的响应。** 推迟。它需要新增失败 code、改动默认可重试集,还需要一个不得覆盖提供方已给出终止原因的 `[DONE]` 闸门——这些代价与风险,已报告的缺陷并不需要承担。它所防的宽松线上格式是假想的:没有任何报告描述过完全不发送身份的流。
+
+**放宽会话读取端对空 `callId` 的拒绝。** 否决:空 `callId` 无法在下一次请求中与提供方配对,接受它只是把失败推进模型请求。该拒绝是持久化边界的闸门;缺陷在生产方。
+
+## 后果
+
+重复发送空或 null 身份的续传分片不产生作用,调用因此保有其首个分片建立的身份,通往 `unknown tool ""` 与不可读会话的已报告路径就此切断。完全不携带身份的流仍会组装出空身份,与改动前一致;该路径以及已经写入空 `callId` 的会话恢复都不在本次改动范围内。
+
+## 测试
+
+`translate.spec.ts` 覆盖空与 null 续传分片、重复的相同身份,以及空续传下并行调用各自保有身份。原有那些描述完全不发送身份的线上格式的用例,保留其记录的空身份输出。

+ 6 - 0
.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.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-09-02-projcache-cross-version-read-compat.md
+2026-09-02-projcache-cross-version-read-compat.md: 1d157d90b04fe92e86d858a8e5424a5e517721f6
+2026-09-02-projcache-cross-version-read-compat.zh.md: 3948126271a84810abf4fe1c1e6e874b279a4f5e

+ 74 - 0
.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.md

@@ -0,0 +1,74 @@
+# Agent Note: Projection-cache cross-version read compatibility (session_projcache v3/v4/v5 → v6)
+
+Status: implemented
+
+English | [中文](2026-09-02-projcache-cross-version-read-compat.zh.md)
+
+## Problem
+
+The `session_projcache` storage domain evolved through three on-disk generations across published releases. An upgraded DSH_HOME failed in two ways:
+
+- **A v3 single-file home bricked startup after the upgrade**: the per-record layout's legacy bootstrap migrated the old whole-unit file without checking its `unit.version`, stamping the old records with the current version into the new tree; the domain layer's per-record zod validation at open then hit the missing now-required fields → `invalid-record` → the whole domain refused to open → the plugin tree failed to load. And because the bootstrap writes before validation runs, **the first boot permanently wrote the bad documents into the new tree** ("poisoning") — every later boot saw a non-empty tree, never took the legacy path again, and the home stayed unusable.
+- **A v4 per-record home lost its listing titles after the upgrade**: v4 documents were silently discarded by the version-stamp check (the per-record contract), and SessionList is a zero-I/O cache-only read, so a miss served the row without projections; titles only returned as each session was individually reopened.
+
+The cache domain's own contract is "a stale or unreadable cache costs a longer tail replay, never a wrong value, never a refused load" — the hard failure and the wholesale discard each broke the first half of that contract or the product expectation.
+
+## The three on-disk generations
+
+| domain version | shipped in | layout | on-disk form | identity fields | row fields |
+|---|---|---|---|---|---|
+| 3 | 0.1.1-rc.2 | single | one file `storages/session_projcache.json` (`{unit:{name,version}, global, tables}`) | `createdAt`, `cwd?` | `ver`, `seq`, `val` |
+| 4 | 0.1.2-alpha.3 | per-record | one file per session `storages/session_projcache/sessions/<sessionId>.json` (`{version, record}`) | `createdAt`, `cwd?` | same |
+| 5 | 0.1.2-alpha.4 | per-record | same as v4 | + `isSeeded` (shipped required; now optional), `inheritedEventCount` (same) | same (`seq` numbers mean the same as v4; only type brands were added) |
+
+The only substantive v4→v5 difference is the two new lineage identity fields; the `ver/seq/val` row shape is identical across all three generations, and `seq` numbering did not change ([the 2026-08-31 seq/offset brands note](2026-08-31-session-sequence-and-log-offset-brands.md) pins the on-disk numbers as unchanged). v3→v4 was a layout migration with identical record content.
+
+One derived shape also exists: a v3 home that ran the v5 build once (the poisoned state) — its new tree holds documents **stamped 5 whose content is a v3 record** (no lineage fields).
+
+## Decision
+
+Declared read compatibility — reads tolerate vouched-for older versions, writes always stamp the current one:
+
+1. **`DomainSpec.compatibleVersions` (new, optional)**: the domain owner declares "records stored under these older versions are also readable under the current record schemas" (typically by declaring the fields old records lack as optional). `defineDomain` validates each entry as a non-negative integer below the current version; `descriptorOf` projects the set onto the backend `KvUnitDescriptor`.
+2. **json backend per-record reads** accept version stamps in "current ∪ compatibleVersions"; anything outside the set is still discarded as foreign. **The write path always stamps the current version** (the first checkpoint after reading an old record naturally advances it). The `single` layout stays exact-version.
+3. **Legacy-bootstrap version gate (the actual bug fix)**: the old whole-unit file's `unit.version` must fall inside the accepted set to be migrated; otherwise the file is left alone and the unit reads empty — stamping records the owner never vouched for turns a discardable stale cache into hard schema failures at the domain layer.
+4. **The projcache domain declares `version: 6, compatibleVersions: [3, 4, 5]`**, and the two lineage fields become `.optional()`. The single reader of stored identities, `identityMatches`, normalizes absence to the unseeded lineage (`?? false` / `?? 0`): exact for unforked sessions, while a forked session's expectation is seeded → natural mismatch → discard and cold rebuild, so the lineage binding loses none of its protection.
+5. **The poisoned state self-heals**: documents stamped 5 without lineage fields are declared compatible and parse under the optional schema (their content is the real pre-upgrade cache data), so the home boots again and titles serve immediately.
+6. **Schema-validation backstop: `invalidRecords: 'backup-and-skip'` (declared by this domain only)**. A stored record that still fails to parse beyond read compatibility no longer refuses the whole domain: the domain layer calls the backend's `KvUnit.backupRecord` (json per-record implementation = rename the document to `<key>.json.bak.<YYYYMMDDHHmm>`, bytes kept, never read again), prints the concrete failure with `logger.error` (domain, table, key, destination, zod cause), and continues the open with the record absent; the next cold read rebuilds and rewrites that session's cache. **The policy is an explicit per-domain declaration and the default stays fail-loud** — other domains still refuse the whole load on invalid stored data, and a backend without `backupRecord` (single layout, row stores) also falls back to fail-loud. Naming history: quarantine → backup-and-skip (user ruling: the word must carry both "back up" and "skip", sharing its root with the `.bak` suffix; skip-backup was rejected because the CLI `--skip-X` convention reads it as "do not back up"). For this domain it supersedes the reset/destroy recovery path of the [2026-07-28 storage recovery proposal](../../proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md), which stays live for authoritative and whole-medium damage.
+
+### v5 → v6 compatibility
+
+Version 6 changes only the current write stamp and keeps the v5 record schema. `compatibleVersions: [3, 4, 5]` therefore admits both healthy v5 records and v5-stamped lineage-less records produced by the faulty bootstrap. The current schema accepts absent lineage; `identityMatches` interprets it as unseeded and rejects the record for a seeded session. The next successful checkpoint rewrites an accepted v5 record with a v6 stamp and complete lineage. No separate v5→v6 rewrite runs at startup: an unaccepted version reads as absent, while a schema-invalid accepted record follows `backup-and-skip`.
+
+### Upgrade matrix
+
+| home shape | behavior after the fix |
+|---|---|
+| v3 single-file (not poisoned) | bootstrap migrates (3 ∈ accepted set) → titles serve immediately |
+| v3 + poisoned new tree | new-tree documents read directly (optional tolerance) → boot restored, titles serve immediately |
+| v4 per-record | documents read directly (4 ∈ accepted set) → titles serve immediately |
+| v5 healthy | documents read directly (5 ∈ accepted set) → titles serve immediately |
+| v6 current | unaffected |
+| old records of forked (seeded) sessions | identity mismatch → discarded, cold rebuild when the session opens (safe side) |
+
+## Alternatives considered
+
+- **Discard-and-rebuild only** (bootstrap gate without compatible versions): fixes the boot, but every SessionList title is lost after the upgrade until each session is reopened — fails the upgrade-and-go product requirement.
+- **Schema `.default()` fills**: behaviorally equivalent to optional + reader normalization, but bakes the "absent = unseeded" interpretation into the durable schema's output type; ruled for optional — the schema honestly describes every accepted on-disk shape and the interpretation lives at the consumer (user ruling, 2026-09-02).
+- **Roll the domain version back to 4**: a small diff, but breaks version monotonicity, depends on the "bootstrap skips no versions" bug itself, and drops every poisoned and healthy v5 home's cache.
+
+## Consequences
+
+- A deployment routing this domain to the sqlite backend gets none of the tolerance: sqlite implements neither `compatibleVersions` nor `backupRecord`, so behavior degrades to the old strict-version semantics (a whole-unit version mismatch still refuses with `version-mismatch`; nothing loosens, nothing serves wrong values). Shipped compositions route this domain to json, so this stays a deployment-configuration risk only.
+- The optional lineage fields let accepted records omit lineage: a lineage-less record decodes as unseeded. The identity match still refuses it for seeded callers, and the per-row `ver` guard still screens every value, so the residual exposure is an unseeded caller reading an unseeded-shaped record — the same trust extended to genuine pre-lineage records.
+- `backupRecord` overwrites a same-minute backup of the same key (the newer bytes win); distinct minutes and distinct keys never collide.
+
+## Testing
+
+- `storage-json` unit tests: compat-stamped reads / out-of-set discards / writes stamping current; legacy bootstrap migrating only accepted versions (including the migrated-documents-stamp-current assertion); `backupRecord` move / absent read / rewrite / closed guard.
+- `storage-domain` unit tests: `compatibleVersions` / `invalidRecords` declaration validation; backup-and-skip falling back to fail-loud when the backend has no `backupRecord`.
+- `session-projection-cache` unit tests: records without lineage fields serve unseeded sessions verbatim and are discarded for seeded ones.
+- **Archived-fixture recovery tests** (`tests/fixtures.spec.ts` + `tests/fixtures/`): four media archives produced by the real released builds — `v3-single-unit.json` (the 0.1.1-rc.2 whole-unit file), `v4-session-doc.json` (0.1.2-alpha.3), `v5-session-doc.json` (0.1.2-alpha.4), `v5-lineageless-doc.json` (the unguarded bootstrap's poisoned shape, synthesized from the v3 record) — each opened through the real storage stack, asserting the listing serves the archived title and that a live write rewrites the document to the current version (v6 stamp + lineage fields + fresh value); plus the backup-and-skip behavior for a schema-failing record (boot survives, `.bak` lands, log is concrete, neighbor records unharmed).
+- End-to-end acceptance, executed against the real release artifacts: the published 0.1.1-rc.2 and 0.1.2-alpha.3 npm builds seeded homes through their own web apps (model turns plus a rename RPC), the published 0.1.2-alpha.4 build reproduced both failures (including the poisoned tree), and the fixed build served every home shape — pristine v3, poisoned v3, v4, and fresh — with the SessionList RPC returning the recorded titles verbatim.
+
+Future bump procedure: when a new version's shape can tolerate old records through "optional fields + reader normalization", add the old version to `compatibleVersions`; otherwise bump normally (discard and rebuild) and remove the no-longer-compatible versions from the set. Either way, the package README requires the bump to land with archived fixtures and tests proving the chosen disposition.

+ 74 - 0
.agents/notes/implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md

@@ -0,0 +1,74 @@
+# Agent Note: 投影缓存跨版本读兼容(session_projcache v3/v4/v5 → v6)
+
+Status: implemented
+
+[English](2026-09-02-projcache-cross-version-read-compat.md) | 中文
+
+## 问题
+
+`session_projcache` 存储域在已发布版本间演进了三代磁盘结构。升级后的 DSH_HOME 出现两类故障:
+
+- **v3 单文件 home 升级后启动硬失败**:per-record 布局的 legacy bootstrap 迁移旧单文件时不检查其 `unit.version`,把旧记录原样打上当前版本戳写入新树;domain 层开域时逐条 zod 校验,旧记录缺新增必填字段 → `invalid-record` → 整个域拒开 → 插件树加载失败。且 bootstrap 先写盘后校验,**首次启动即把坏文档永久写入新树**("投毒")——此后每次启动新树非空、连 legacy 路径都不再走,home 持续不可用。
+- **v4 per-record home 升级后列表丢标题**:v4 文档被版本戳检查静默丢弃(per-record 契约),SessionList 是零 I/O 纯缓存读,miss 后整行不带投影;标题要等每个会话被逐个重新打开后才恢复。
+
+缓存域自身的契约是"过期或不可读的缓存只付出更长的尾部重放,绝不给出错值、绝不拒载"——硬失败与整体丢弃都违背该契约的前半句或产品预期。
+
+## 三代磁盘结构差异
+
+| domain version | 携带发布 | 布局 | 磁盘形态 | identity 字段 | 行字段 |
+|---|---|---|---|---|---|
+| 3 | 0.1.1-rc.2 | single | 单文件 `storages/session_projcache.json`(`{unit:{name,version}, global, tables}`) | `createdAt`, `cwd?` | `ver`, `seq`, `val` |
+| 4 | 0.1.2-alpha.3 | per-record | 每会话一份 `storages/session_projcache/sessions/<sessionId>.json`(`{version, record}`) | `createdAt`, `cwd?` | 同上 |
+| 5 | 0.1.2-alpha.4 | per-record | 同 v4 | + `isSeeded`(v5 首发必填;现为 optional)、`inheritedEventCount`(同前) | 同上(`seq` 数值语义与 v4 相同,仅类型加 brand) |
+
+v4→v5 的唯一实质差异是 identity 新增两个 lineage 字段;行内 `ver/seq/val` 三代一致,`seq` 的数值含义未变([2026-08-31 seq/offset brands note](2026-08-31-session-sequence-and-log-offset-brands.zh.md) 明确 on-disk 数值不变)。v3→v4 是布局迁移,记录内容结构一致。
+
+另有一种衍生形态:跑过一次 v5 版本的 v3 home(投毒态)——新树里存在**版本戳为 5 但内容是 v3 记录**(缺 lineage 字段)的文档。
+
+## 决策
+
+声明式读兼容——读容忍 owner 背书过的旧版本,写恒戳当前版本:
+
+1. **`DomainSpec.compatibleVersions`(新增,可选)**:域 owner 声明"这些旧版本的存量记录在当前记录 schema 下也可读"(典型手段:新增字段标 optional)。`defineDomain` 校验各项为小于当前 version 的非负整数;`descriptorOf` 透传到后端 `KvUnitDescriptor`。
+2. **json 后端 per-record 读**:接受"当前版本 ∪ compatibleVersions"内的版本戳,集合外照旧视为 foreign 丢弃;**写路径永远戳当前版本**(读到旧记录后的下一次 checkpoint 自然把它推进到当前版本)。single 布局维持 exact-version 不变。
+3. **legacy bootstrap 版本把关(bug 修复本体)**:旧单文件的 `unit.version` 必须落在接受集合内才迁移,否则视为空 unit 留在原地——为 owner 未背书的记录打当前版本戳,会把"可丢弃的过期缓存"变成 domain 层的 schema 硬失败。
+4. **projcache 域声明 `version: 6, compatibleVersions: [3, 4, 5]`**;两个 lineage 字段改为 `.optional()`。唯一消费 stored identity 的读点 `identityMatches` 把缺失归一化为 unseeded lineage(`?? false` / `?? 0`):对非 fork 会话这是精确值;fork 会话的 expected 是 seeded → 天然 mismatch → 丢弃冷读重建,lineage 绑定的防护不放松。
+5. **投毒态自愈**:v5 戳缺 lineage 字段的文档被声明为兼容,并由 optional schema 接受(内容本就是升级前的真实缓存数据),home 恢复可启动且标题立即可服务。
+6. **schema 校验兜底:`invalidRecords: 'backup-and-skip'`(仅本域声明)**。读兼容之外仍然解析失败的存量记录不再让整个域拒开:domain 层调用后端的 `KvUnit.backupRecord`(json per-record 实现=把文档改名为 `<key>.json.bak.<YYYYMMDDHHmm>`,字节留档、不再被读取),用 `logger.error` 打印具体失败信息(域名、表、键、移动去向、zod 失败原因),随后当该记录不存在继续启动;下一次冷读会重建并重写该会话的缓存。**该策略是域级显式声明,缺省仍为 fail-loud**——其他业务域的存量数据校验失败照旧整域拒载;后端没有 `backupRecord` 能力(single 布局、行存储)时也回退 fail-loud。命名沿革:quarantine → backup-and-skip(用户裁决:词要同时含"备份"与"跳过"两义,且与 `.bak` 后缀同源;skip-backup 因 CLI `--skip-X` 惯例存在"不备份"反读而弃用)。对本域而言,该策略取代了 [2026-07-28 存储恢复提案](../../proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md)中 reset/destroy 的恢复途径;该提案对权威介质与整介质损坏仍然有效。
+
+### v5 → v6 兼容方式
+
+版本 6 只改变当前写入的版本戳,沿用 v5 记录 schema。`compatibleVersions: [3, 4, 5]` 因此同时接受健康的 v5 记录,以及错误 bootstrap 生成的 v5 戳、缺 lineage 记录。当前 schema 允许 lineage 缺失;`identityMatches` 将其解释为 unseeded,并对 seeded 会话拒绝该记录。下一次成功的 checkpoint 会用 v6 戳和完整 lineage 重写已接受的 v5 记录。启动时不单独运行 v5→v6 重写:未接受的版本读作不存在,schema 校验失败的已接受记录则执行 `backup-and-skip`。
+
+### 升级矩阵
+
+| home 形态 | 修复后行为 |
+|---|---|
+| v3 单文件(未投毒) | bootstrap 迁移(3 ∈ 接受集)→ 标题立即可服务 |
+| v3 + 投毒新树 | 新树文档直接读入(optional 容忍)→ 启动恢复、标题立即可服务 |
+| v4 per-record | 文档直接读入(4 ∈ 接受集)→ 标题立即可服务 |
+| v5 正常 | 文档直接读入(5 ∈ 接受集)→ 标题立即可服务 |
+| v6 当前版本 | 不受影响 |
+| fork(seeded)会话的旧记录 | identity mismatch → 丢弃,打开会话时冷读重建(安全侧) |
+
+## 备选方案
+
+- **只丢弃重建**(bootstrap 把关但不声明兼容版本):启动可修,但升级后 SessionList 标题全丢、要逐会话打开才恢复——不满足升级即用的产品要求。
+- **schema `.default()` 填缺省**:行为与 optional+读点归一化等价,但把"缺失=unseeded"的解释固化进 durable schema 的输出类型;拍板为 optional——schema 如实描述介质上所有被接受的形态,解释权在消费点(2026-09-02 用户裁决)。
+- **域版本回退到 4**:改动很小,但破坏版本单调性、依赖"bootstrap 不查版本"这个 bug 本身、且投毒态与正常 v5 home 的缓存全被丢弃。
+
+## 影响
+
+- 部署方若把本域路由到 sqlite 后端,得不到任何容忍能力:sqlite 既未实现 `compatibleVersions` 也没有 `backupRecord`,行为退化为原有的严格版本语义(整 unit 版本不匹配仍 `version-mismatch` 拒开;不放松、不出错值)。shipped 组合固定路由 json,此风险仅存在于部署配置层面。
+- optional lineage 字段允许被接受的记录缺少 lineage:无 lineage 的记录会解码为 unseeded。身份比对仍会对 seeded 调用方拒收,逐行 `ver` 守卫仍筛查每个值,残余暴露面只是 unseeded 调用方读到 unseeded 形态的记录——与真实 pre-lineage 记录享有的信任完全相同。
+- `backupRecord` 对同一键的同一分钟内重复备份会覆盖前一份(新字节胜出);不同分钟、不同键永不冲突。
+
+## 测试
+
+- `storage-json` 单测:compat 版本戳读入/集合外丢弃/写恒当前版本;legacy bootstrap 仅在版本被接受时迁移(含迁移后文档戳当前版本断言);`backupRecord` 移档/读缺席/重写/封闭守卫。
+- `storage-domain` 单测:`compatibleVersions`/`invalidRecords` 声明校验;后端无 `backupRecord` 时 backup-and-skip 回退 fail-loud。
+- `session-projection-cache` 单测:缺 lineage 字段的记录对 unseeded 会话按原值服务、对 seeded 会话丢弃。
+- **归档 fixtures 独立恢复测试**(`tests/fixtures.spec.ts` + `tests/fixtures/`):真实发布物产出的四份介质存档——`v3-single-unit.json`(0.1.1-rc.2 整域单文件)、`v4-session-doc.json`(0.1.2-alpha.3)、`v5-session-doc.json`(0.1.2-alpha.4)、`v5-lineageless-doc.json`(无守卫 bootstrap 的投毒形态,由 v3 记录合成)——逐一走真实存储栈开域,断言列表读出归档标题、且 live 写把文档重写为当前版本(v6 戳 + lineage 字段 + 新值);外加 schema 失败记录的 backup-and-skip 行为(启动不失败、`.bak` 落盘、日志具体、邻居记录不受累)。
+- 端到端验收,以真实发布物执行:已发布的 0.1.1-rc.2 与 0.1.2-alpha.3 npm 包经各自 web app 造数(真实模型对话 + rename RPC),已发布的 0.1.2-alpha.4 包复现两类故障(含投毒树),修复后构建对纯净 v3、投毒 v3、v4、全新四种 home 形态经 SessionList RPC 原样返回记录在案的标题。
+
+未来 bump 流程:新版本结构若可用"optional 字段 + 读点归一化"容忍旧记录,就把旧版本加入 `compatibleVersions`;否则正常 bump(丢弃重建),并把不再兼容的版本从集合中移除。无论哪条路,包 README 都要求 bump 随附归档 fixture 和论证所选处置方式的测试。

+ 2 - 2
.agents/notes/implemented/feature/2026-08-05-agent-teams.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-05-agent-teams.md
-2026-08-05-agent-teams.md: fbcd8485a972323bc0f8ffb6a5cb7cca9a50044e
-2026-08-05-agent-teams.zh.md: 0a1a8a81c47abfca275f5fc3c60b8d514cdddd30
+2026-08-05-agent-teams.md: df84b2710d597330e9fdf6f8f83edb1441f100ec
+2026-08-05-agent-teams.zh.md: f1ef275610f6307578288d483eb561ee8d3c7531

+ 3 - 3
.agents/notes/implemented/feature/2026-08-05-agent-teams.md

@@ -32,7 +32,7 @@ Fresh children have no inherited conversation. Fork children capture the Lead's
 
 Peer communication is a Lead-log mailbox. `team/message/queued` is appended and flushed before delivery. The target message carries the stable message id and sender identity in both durable source metadata and a short model-visible prefix. A target receipt is acknowledged with `team/message/delivered` only after its pending inbox item or recorded user message is flushed. Immediate admission is serialized per target in queued-log order, recovery retries queued-minus-delivered in the same order, and delivery folds live or persisted target inbox/history state before cold resume. Every current-version Team payload is runtime-validated before entering replay state. The Team runtime tracks dispatch and asynchronous acknowledgement work from synchronous admission until settlement; disposal closes admission and awaits both before removing the service. Current waiters wake only after the owning Team event flush succeeds.
 
-Quiet `send_message` injects, flushes, and acknowledges immediately for a live target without waking it; an inactive target remains queued until another event materializes that teammate. Waking `followup_task` becomes the target's next FIFO turn and may cold-resume it. Success means the message is already durable even when immediate delivery is deferred. The mechanism provides process-local retry and target-Session de-duplication, not a cross-process exactly-once claim.
+`send_message` always attempts Steer delivery. A running target receives the message at the nearest step boundary, an idle target starts a turn, and an inactive teammate cold-resumes. Success means the message is already durable even when temporary delivery failure leaves it queued. The mechanism provides process-local retry and target-Session de-duplication, not a cross-process exactly-once claim. The [Team Steer messaging decision](../simplification/2026-08-30-team-send-message-steer.md) owns the single-tool scheduling rationale.
 
 Shared tasks are complete snapshots with Team-local ids and monotonic revisions. Every mutation carries `expectedRevision`. Any member creates, reads, or claims a ready unowned task; the owner or Lead edits and transitions it, while only the Lead assigns another member. Numeric task ids remain within the safe-integer allocation range, and exhaustion fails without reusing an id. Dependencies must name non-deleted tasks and form a complete DAG. Deleted tasks are retained tombstones. `writeScopes` are normalized path prefixes that produce overlap diagnostics but never block claim or authorize a write.
 
@@ -48,7 +48,7 @@ Worktree isolation is not a harness runtime behavior. A deployment or prompt may
 
 **Extend direct-child subagent tools with peer ids.** Rejected because parent/child authority and Team peer membership are different domains. Adding peer access to the continuation seam would weaken its exact-parent authorization and still leave roster and tasks without a persistence owner.
 
-**Store mail in each target Session before delivery.** Rejected because an inactive target is intentionally not materialized for quiet mail. The always-live Lead Session is the transaction home; target recording is the acknowledgement and de-duplication boundary.
+**Store mail in each target Session before Lead-log enqueue.** Rejected because target materialization and acceptance can fail after the Team has committed the send. The always-live Lead Session is the transaction home; target recording is the acknowledgement and de-duplication boundary.
 
 **Treat task ownership or write scopes as locks.** Rejected because external writers bypass them, crashed owners remain durable, and path-prefix overlap cannot prove semantic independence. False mutual exclusion is more dangerous than an explicit warning.
 
@@ -68,6 +68,6 @@ Package tests cover identity, name and authority checks, provider selection, res
 
 The Lead Session grows with whole task/member snapshots and mailbox acknowledgements. This favors independently inspectable recovery over compact deltas; configured task and pending-mail bounds cap active state, while deleted and delivered history remains append-only until broader Session retention applies.
 
-An active roster member can be non-resident, so `inactive` is not failure and a wakeup can incur cold-resume latency. A quiet message for an inactive target can remain pending indefinitely until the target is otherwise materialized. A failed member permanently consumes its name and member slot, making provisioning failures visible instead of silently recycling identity.
+An active roster member can be non-resident, so `inactive` is not failure and a send can incur cold-resume latency. Temporary inspection, resume, or inbox-admission failure can leave a durable message queued for recovery. A failed member permanently consumes its name and member slot, making provisioning failures visible instead of silently recycling identity.
 
 Coordination reduces likely checkout conflicts but cannot eliminate writes outside filesystem compare-and-set tools. The final diff and tests remain the Lead's integration boundary.

+ 3 - 3
.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md

@@ -32,7 +32,7 @@ fresh child 不继承对话。fork child 只捕获一次 Lead 已完成 turn 前
 
 Peer 通讯使用 Lead 日志 mailbox。投递前先追加并 flush `team/message/queued`。target message 会在持久 source metadata 与短模型可见前缀中同时携带稳定 message id 和 sender identity。只有 pending inbox 条目或已记录用户消息完成 flush,Lead 日志才写入 `team/message/delivered` acknowledgement。即时准入按 target 和 queued 日志顺序串行化,恢复按同一顺序重试 queued-minus-delivered,并在冷恢复前折叠 live 或 persisted target 的 inbox/历史状态。每个当前版本 Team payload 都会经过运行时验证后才进入 replay state。Team runtime 从同步准入到 settlement 全程跟踪 dispatch 与异步 acknowledgement 工作;dispose 会关闭准入,并在移除服务前等待两者。当前 waiter 只在所属 Team event flush 成功后被唤醒。
 
-对于 live target,quiet `send_message` 会立即注入、flush 并确认,但不会唤醒它;inactive target 会保持 queued,直到其他事件 materialize 该 teammate。waking `followup_task` 成为 target 的下一个 FIFO turn,并可冷恢复。即使即时投递被推迟,成功也表示消息已经持久化。该机制提供进程内重试与 target Session 去重,不宣称跨进程 exactly-once。
+`send_message` 始终尝试 Steer 投递。running target 在最近的步骤边界收到消息,idle target 启动一个轮次,inactive teammate 则冷恢复。即使临时投递失败让消息保持 queued,成功也表示消息已经持久化。该机制提供进程内重试与 target Session 去重,不宣称跨进程 exactly-once。[Team Steer 消息决策](../simplification/2026-08-30-team-send-message-steer.zh.md)负责单工具调度的理由。
 
 共享 task 是带 Team-local id 与单调 revision 的完整快照。每次变更都携带 `expectedRevision`。任意 member 可以创建、读取或 claim ready 且无 owner 的任务;Owner 或 Lead 可以编辑和转换;只有 Lead 可以分配给另一个 member。数字 task id 保持在安全整数分配范围内;该范围耗尽时会失败,不会复用 id。依赖必须指向未删除任务,并形成完整 DAG。删除任务保留为 tombstone。`writeScopes` 是规范化路径前缀,只产生重叠诊断,绝不会阻止 claim 或授予写权限。
 
@@ -48,7 +48,7 @@ Worktree isolation 不是 harness runtime 行为。deployment 或 prompt 可以
 
 **用 peer id 扩展 direct-child subagent tool。** 拒绝,因为 parent/child 权限与 Team peer membership 是不同领域。向 continuation seam 增加 peer access 会削弱 exact-parent authorization,仍无法为 roster 与 task 提供持久 owner。
 
-**投递前把 mail 存入每个 target Session。** 拒绝,因为 quiet mail 不会 materialize inactive target。始终 live 的 Lead Session 是事务 owner;target recording 是 acknowledgement 与去重边界。
+**在 Lead 日志入队前把 mail 存入每个 target Session。** 拒绝,因为 Team 提交发送之后,target materialization 与准入仍可能失败。始终 live 的 Lead Session 是事务 owner;target recording 是 acknowledgement 与去重边界。
 
 **把 task ownership 或 write scope 当作锁。** 拒绝,因为外部 writer 会绕过它们,崩溃 owner 会持久保留,而路径前缀重叠不能证明语义独立。虚假的互斥保证比明确 warning 更危险。
 
@@ -68,6 +68,6 @@ Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、p
 
 Lead Session 会随着完整 task/member 快照与 mailbox acknowledgement 增长。该设计用可独立检查的恢复能力换取更紧凑的 delta;配置的 task 与 pending-mail 限额限制 active state,而 deleted 与 delivered 历史会保持 append-only,直到更广泛的 Session retention 生效。
 
-active roster member 可以不驻留,因此 `inactive` 不表示失败,wakeup 可能产生 cold-resume 延迟。发往 inactive target 的 quiet message 可能无限等待,直到 target 因其他原因 materialize。failed member 会永久占用名字与 member slot,使 provisioning failure 保持可见而不是静默回收身份。
+active roster member 可以不驻留,因此 `inactive` 不表示失败,send 可能产生 cold-resume 延迟。临时 inspection、resume 或 inbox 准入失败可能留下持久 queued 消息等待恢复。failed member 会永久占用名字与 member slot,使 provisioning failure 保持可见而不是静默回收身份。
 
 协调可以降低 checkout 冲突概率,但无法消除文件系统 CAS 工具之外的写入。最终 diff 与测试仍是 Lead 的集成边界。

+ 2 - 2
.agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.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-31-pr-opened-issue-start-dates.md
-2026-08-31-pr-opened-issue-start-dates.md: f8976b8b0499aa5c68c9637e8571805b78ce6d48
-2026-08-31-pr-opened-issue-start-dates.zh.md: 3142bad5007cbbdd27e1f564bc3ccec9101d3d79
+2026-08-31-pr-opened-issue-start-dates.md: 49756d9960a7616993e4513c20c990e5cfba167e
+2026-08-31-pr-opened-issue-start-dates.zh.md: 752533251be559cb4fe82f619553872bbcd748d1

+ 8 - 6
.agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.md

@@ -6,26 +6,28 @@ English | [中文](2026-08-31-pr-opened-issue-start-dates.zh.md)
 
 ## Problem
 
-The Issue Project records planned work in a `Start date` field, but adding or linking an Issue does not provide a date value. A pull request can identify both Issues it resolves and Issues that supply related implementation context, and either relationship marks the start of repository work.
+The organization-level `Start date` Issue field records when work begins, but adding an Issue to the Issue Project or linking it from a pull request does not provide a date value. A pull request can identify both Issues it resolves and Issues that supply related implementation context, and either relationship marks the start of repository work.
 
 Updating the field on every pull-request event would assign dates to existing work after edits, pushes, or reopenings. Replacing an existing date would also discard a manually planned date or a date recorded by an earlier pull request.
 
 ## Decision
 
-The Issue lifecycle workflow initializes `Start date` only for `pull_request.opened`. It reads the pull request's live body, retains every same-repository reference that resolves to an Issue, converts `created_at` to a calendar date in the configured Project time zone, ensures the Issue is a Project item, and writes the configured Date field only when the current value is empty.
+The Issue lifecycle workflow initializes `Start date` only for `pull_request.opened`. It reads the pull request's live body, retains every same-repository reference that resolves to an Issue, converts `created_at` to a calendar date in the configured Project time zone, ensures the Issue is a Project item, and writes the configured organization Issue Date field only when the current value is empty.
 
-The configuration names the Project field and time zone. Missing configuration fails when the policy module loads; a missing field, a non-Date field, an invalid timestamp, or a failed API request fails the workflow at the first relevant pull request.
+The configuration names the field exposed in the Project and the time zone. The Project field must resolve to an organization Issue Date field; the workflow reads its Issue value and updates it through `updateIssueFieldValue`. Missing configuration fails when the policy module loads; a missing field, a non-Date or Project-local field, an invalid timestamp, or a failed API request fails the workflow at the first relevant pull request.
 
 [Event-directed PR review status commands](2026-08-10-event-directed-pr-review-status.md) continue to own Status transitions. Date initialization includes resolving and informational Issue references, runs for Draft and automated pull requests, and does not depend on PR policy enforcement.
 
 ## Verification
 
-[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) cover the Shanghai date boundary, opened-only dispatch, all retained Issue references, empty-value writes, existing-value preservation, missing Project items, invalid field configuration, and the GraphQL mutation variables. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) require the `pull_request.opened` subscription.
+[Issue-management tests](../../../../.github/issue-management/policy.test.mjs) cover the Shanghai date boundary, opened-only dispatch, all retained Issue references, Issue-field discovery, empty-value writes, existing-value preservation, missing Project items, invalid field configuration, and the `updateIssueFieldValue` variables. [Workflow tests](../../../../scripts/ci-workflow.spec.ts) require the `pull_request.opened` subscription.
 
 ## Alternatives considered
 
 **Use a built-in Project workflow.** The built-in workflows own fixed Project item and Status transitions; the repository workflow already owns authenticated GraphQL mutations and can supply the PR creation date.
 
+**Use a Project-local Date field.** A Project field would allow different dates for the same Issue in different Projects and would not appear on the Issue itself. Work begins for the Issue rather than for one Project membership, so the organization Issue field owns the value.
+
 **Process every subscribed PR event or run a reconciler.** Later events would fill dates for existing pull requests and references added after creation, but they would make the field a repair projection instead of a record created with the pull request and would add repeated Project reads.
 
 **Update only resolving Issue references.** Informational references also identify Issues whose implementation work begins with the pull request, so the date initializer uses the existing all-reference set while Status transitions retain resolving-only semantics.
@@ -34,6 +36,6 @@ The configuration names the Project field and time zone. Missing configuration f
 
 ## Consequences
 
-Only pull requests opened after the workflow ships initialize dates. References added after creation and existing open pull requests remain unchanged, and the workflow does not scan existing Project items or pull requests.
+Only pull requests opened after the workflow ships initialize dates. References added after creation and existing open pull requests remain unchanged, and the workflow does not scan existing Project items or pull requests. The date follows the Issue across organization Projects that expose the field.
 
-The empty-value read makes retries idempotent in ordinary operation. ProjectV2 has no conditional field update, so simultaneous pull requests that reference the same empty Issue can both write; per-PR concurrency does not serialize that Issue, and the last mutation can win.
+The empty-value read makes retries idempotent in ordinary operation. The Issue-field mutation has no compare-and-set precondition, so simultaneous pull requests that reference the same empty Issue can both write; per-PR concurrency does not serialize that Issue, and the last mutation can win.

+ 8 - 6
.agents/notes/implemented/process/2026-08-31-pr-opened-issue-start-dates.zh.md

@@ -6,26 +6,28 @@ Status: implemented
 
 ## 问题
 
-Issue Project 使用 `Start date` 字段记录已规划工作的开始日期,但加入或关联 Issue 不会提供日期值。PR 可以同时标识它所解决的 Issue 和提供相关实现上下文的 Issue;两种关系都表示仓库工作已经开始。
+组织级 `Start date` Issue 字段记录工作开始时间,但把 Issue 加入 Issue Project 或从 PR 关联它都不会提供日期值。PR 可以同时标识它所解决的 Issue 和提供相关实现上下文的 Issue;两种关系都表示仓库工作已经开始。
 
 如果每个 PR 事件都更新该字段,编辑、推送或重新打开 PR 会为已有工作补上日期。覆盖已有日期还会丢弃人工规划的日期或较早 PR 记录的日期。
 
 ## 决策
 
-Issue 生命周期工作流仅在 `pull_request.opened` 时初始化 `Start date`。工作流读取 PR 的实时正文,保留每个能解析为 Issue 的同仓库引用,把 `created_at` 按配置的 Project 时区转换为日历日期,确保 Issue 是 Project item,并仅在当前值为空时写入配置的 Date 字段。
+Issue 生命周期工作流仅在 `pull_request.opened` 时初始化 `Start date`。工作流读取 PR 的实时正文,保留每个能解析为 Issue 的同仓库引用,把 `created_at` 按配置的 Project 时区转换为日历日期,确保 Issue 是 Project item,并仅在当前值为空时写入配置的组织级 Issue Date 字段。
 
-配置指定 Project 字段和时区。配置缺失会在策略模块加载时失败;字段缺失、字段不是 Date 类型、时间戳无效或 API 请求失败会让首个相关 PR 的工作流失败。
+配置指定 Project 中显示的字段和时区。该 Project 字段必须解析为组织级 Issue Date 字段;工作流读取它的 Issue 值并通过 `updateIssueFieldValue` 更新。配置缺失会在策略模块加载时失败;字段缺失、字段不是 Date 类型或是 Project 局部字段、时间戳无效或 API 请求失败会让首个相关 PR 的工作流失败。
 
 [由事件直接指定的 PR 评审状态命令](2026-08-10-event-directed-pr-review-status.zh.md)继续负责 Status 转换。日期初始化同时包含解决型和信息型 Issue 引用,对 Draft PR 和自动化 PR 同样运行,也不依赖 PR 策略检查是否生效。
 
 ## 验证
 
-[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)覆盖上海时区日期边界、仅 opened 分派、全部保留的 Issue 引用、空值写入、已有值保留、Project item 缺失、字段配置无效和 GraphQL mutation 变量。[工作流测试](../../../../scripts/ci-workflow.spec.ts)要求保留 `pull_request.opened` 订阅。
+[Issue 管理测试](../../../../.github/issue-management/policy.test.mjs)覆盖上海时区日期边界、仅 opened 分派、全部保留的 Issue 引用、Issue 字段发现、空值写入、已有值保留、Project item 缺失、字段配置无效和 `updateIssueFieldValue` 变量。[工作流测试](../../../../scripts/ci-workflow.spec.ts)要求保留 `pull_request.opened` 订阅。
 
 ## 考虑过的替代方案
 
 **使用 Project 内置工作流。** 内置工作流负责固定的 Project item 和 Status 转换;仓库工作流已经负责经过身份验证的 GraphQL mutation,并且能够提供 PR 创建日期。
 
+**使用 Project 局部 Date 字段。** Project 字段允许同一个 Issue 在不同 Project 中使用不同日期,并且不会显示在 Issue 自身。工作是针对 Issue 开始,而不是针对某次 Project 归属开始,因此由组织级 Issue 字段持有该值。
+
 **处理每个已订阅 PR 事件或运行协调器。** 后续事件可以为已有 PR 和创建后新增的引用补上日期,但这会让该字段成为修复型投影,而不是随 PR 创建的记录,并且会增加重复 Project 读取。
 
 **仅更新解决型 Issue 引用。** 信息型引用同样标识随该 PR 开始实现工作的 Issue,因此日期初始化使用现有的全部引用集合,Status 转换仍只处理解决型引用。
@@ -34,6 +36,6 @@ Issue 生命周期工作流仅在 `pull_request.opened` 时初始化 `Start date
 
 ## 后果
 
-只有工作流发布后新建的 PR 会初始化日期。创建后新增的引用和现有开放 PR 保持不变,工作流不会扫描已有 Project item 或 PR。
+只有工作流发布后新建的 PR 会初始化日期。创建后新增的引用和现有开放 PR 保持不变,工作流不会扫描已有 Project item 或 PR。日期会随 Issue 出现在组织内显示该字段的各个 Project 中。
 
-空值读取使重试在通常情况下保持幂等。ProjectV2 没有条件字段更新,因此同时引用同一个空日期 Issue 的 PR 可能都会写入;按 PR 设置的并发控制不会串行化该 Issue,最后一次 mutation 可能胜出。
+空值读取使重试在通常情况下保持幂等。Issue 字段 mutation 没有比较并设置前提,因此同时引用同一个空日期 Issue 的 PR 可能都会写入;按 PR 设置的并发控制不会串行化该 Issue,最后一次 mutation 可能胜出。

+ 6 - 0
.agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.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/simplification/2026-08-30-team-send-message-steer.md
+2026-08-30-team-send-message-steer.md: 9eb38ec96f51d074510bd918fc46bac9a61027d0
+2026-08-30-team-send-message-steer.zh.md: 44ed792a1c1a4e149c3309a538dfbe6418612b16

+ 41 - 0
.agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.md

@@ -0,0 +1,41 @@
+# Agent Note: Team messaging uses one Steer send_message operation
+
+Status: implemented
+
+English | [中文](2026-08-30-team-send-message-steer.zh.md)
+
+## Problem
+
+Agent Teams exposed two model operations for one durable mailbox: quiet `send_message` injected into a live target without waking it, while `followup_task` queued a distinct waking turn and cold-resumed an inactive teammate. Models had to choose a scheduling policy instead of stating whom to message, and quiet messages could accumulate for an inactive teammate until unrelated work resumed it.
+
+The ordinary continuable-Agent controls already use one direction-neutral `send_message` with fixed Steer scheduling. Retaining separate Team names and delivery modes made equivalent model communication depend on whether the target happened to be a direct child or a Team peer.
+
+## Decision
+
+Every Team member receives one `send_message({ target, message })` tool. The Team tool set contains nine operations; `followup_task` and model-selectable quiet delivery are absent. The durable `TeamMessageSnapshot` stores sender, target, content, and message identity without a scheduling field.
+
+Every accepted Team message uses Steer. A running target receives it at the nearest step boundary, an idle target starts a turn, and an inactive teammate cold-resumes through the continuation lifecycle. A successful Team send remains durable before delivery starts. `accepted` means the target inbox accepted the message; `queued` means a temporary inspection, resume, or inbox-admission failure left it in the Team mailbox for recovery. Neither result means the target completed the requested work.
+
+The Lead receives the Team-attributed user message through `Agent.steer()`. A teammate receives it through a symbol-keyed host-only continuation adapter that authorizes the exact Lead-to-direct-child edge, preserves the original `TeamMessageSource`, and performs resident or cold-resume Steer admission. Sibling and teammate-to-Lead messages therefore retain the real sender; the Team runtime never calls public adjacent-Agent `sendMessage()` while impersonating the Lead.
+
+The Lead Session remains the mailbox transaction owner. It flushes `team/message/queued` before dispatch, serializes immediate admissions per target in Lead-log order, and records `team/message/delivered` only after the target Session durably contains the same Team message id. Recovery retries queued-minus-delivered records in order, and target-side source folding prevents duplicate acceptance across the crash window between inbox insertion and acknowledgement.
+
+## Alternatives considered
+
+**Keep quiet `send_message` and waking `followup_task`.** This preserves caller control over turn scheduling but makes the model choose an implementation policy, permits unread durable mail on inactive targets, and diverges from adjacent-Agent messaging.
+
+**Keep `followup_task` as an alias for Steer.** Two names for identical behavior would preserve the tool-selection failure without adding an observable capability.
+
+**Route siblings through public adjacent-Agent `sendMessage()`.** That operation authorizes only exact direct-parent or direct-child model senders and derives its own `AgentMessageSource`. Calling it with the Lead would misattribute sibling mail; widening it to Team membership would weaken its adjacency rule.
+
+**Drop the Team mailbox and deliver directly.** Direct delivery loses durable enqueue-before-admission, recovery after temporary failure, stable message ids, and target-side de-duplication.
+
+## Testing
+
+Package tests pin running, idle, inactive, Lead, sibling, and recovery delivery; target-local ordering; sender attribution; inbox/history de-duplication; temporary failure returning `queued`; and the nine-tool schema. The keyless Agent Teams profile snapshot drives a running implementer, steers a researcher message into its next step, and verifies that both teammates still complete their assigned tasks before the Lead aggregates the result.
+
+## Consequences
+
+Models have one Team communication choice and cannot park quiet information intentionally. A message may extend the target's current turn, so prompts and tests require teammates to integrate new messages without abandoning work already in progress.
+
+The host-only Steer adapter becomes part of the internal continuation integration used by Team delivery. Human browser prompts keep the separate Queue adapter and remain distinct turns. The broader [Agent Teams decision](../feature/2026-08-05-agent-teams.md) retains mailbox, roster, task, and shared-checkout ownership; the [adjacent-Agent messaging decision](../architecture/2026-08-27-adjacent-agent-steer-messaging.md) retains the public direct-edge authorization and model-message source.

+ 41 - 0
.agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.zh.md

@@ -0,0 +1,41 @@
+# Agent Note: Team 消息使用单一 Steer send_message 操作
+
+Status: implemented
+
+[English](2026-08-30-team-send-message-steer.md) | 中文
+
+## 问题
+
+Agent Teams 为一个持久 mailbox 公开了两个模型操作:quiet `send_message` 注入 live target 而不唤醒它,`followup_task` 则排入一个独立 waking turn 并冷恢复 inactive teammate。模型必须选择调度策略,而不是只说明消息目标;quiet 消息可能为 inactive teammate 持续累积,直到无关工作恢复它。
+
+普通 continuable-Agent 控件已经使用一个方向无关、固定 Steer 调度的 `send_message`。保留独立的 Team 名称与投递模式,会让等价的模型通信因为 target 恰好是 direct child 还是 Team peer 而采用不同语义。
+
+## 决策
+
+每个 Team member 都会获得一个 `send_message({ target, message })` 工具。Team 工具集包含九个操作;不存在 `followup_task` 与模型可选的 quiet 投递。持久 `TeamMessageSnapshot` 存储 sender、target、content 与 message identity,不存储调度字段。
+
+每条已接受的 Team 消息都使用 Steer。running target 在最近的步骤边界收到消息,idle target 启动一个轮次,inactive teammate 则通过 continuation lifecycle 冷恢复。每次成功的 Team send 都会在开始投递前完成持久化。`accepted` 表示 target inbox 已接受消息;`queued` 表示临时 inspection、resume 或 inbox 准入失败让消息留在 Team mailbox 等待恢复。两种结果都不表示 target 已完成所请求的工作。
+
+Lead 通过 `Agent.steer()` 接收携带 Team 归因的用户消息。teammate 通过 symbol-keyed host-only continuation adapter 接收消息;该 adapter 会授权精确的 Lead-to-direct-child edge、保留原始 `TeamMessageSource`,并执行 resident 或 cold-resume Steer 准入。因此 sibling 与 teammate-to-Lead 消息保留真实 sender;Team 运行时绝不会伪装成 Lead 调用公开的相邻 Agent `sendMessage()`。
+
+Lead Session 继续作为 mailbox transaction owner。它在 dispatch 前 flush `team/message/queued`,按 Lead 日志顺序为每个 target 串行化即时准入,并且只有 target Session 持久包含相同 Team message id 后才记录 `team/message/delivered`。恢复按顺序重试 queued-minus-delivered 记录;target 侧 source 折叠会防止 inbox insertion 与 acknowledgement 之间的 crash window 导致重复准入。
+
+## 考虑过的替代方案
+
+**保留 quiet `send_message` 与 waking `followup_task`。** 这会保留调用方对 turn 调度的控制,但要求模型选择实现策略、允许 inactive target 存在未读持久 mail,并与相邻 Agent 消息语义分叉。
+
+**保留 `followup_task` 作为 Steer 别名。** 两个名字表达同一行为只会保留工具选择错误,不会增加可观察能力。
+
+**通过公开的相邻 Agent `sendMessage()` 路由 sibling。** 该操作只授权精确的 direct-parent 或 direct-child 模型 sender,并派生自己的 `AgentMessageSource`。以 Lead 身份调用会错误归因 sibling mail;把它扩展到 Team membership 则会削弱相邻关系规则。
+
+**删除 Team mailbox 并直接投递。** 直接投递会失去准入前持久入队、临时失败后的恢复、稳定 message id 与 target 侧去重。
+
+## 测试
+
+包测试固定 running、idle、inactive、Lead、sibling 与 recovery 投递,target-local ordering、sender attribution、inbox/history 去重、临时失败返回 `queued`,以及九工具 schema。无密钥 Agent Teams profile snapshot 驱动 running implementer,把 researcher 消息 Steer 到其下一步骤,并验证两个 teammate 都继续完成各自任务,之后 Lead 才汇总结果。
+
+## 后果
+
+模型只有一种 Team 通信选择,不能有意停放 quiet information。一条消息可能扩展 target 的当前 turn,因此提示词与测试要求 teammate 整合新消息,同时不放弃已经进行的工作。
+
+host-only Steer adapter 成为 Team 投递使用的内部 continuation 集成。人类浏览器 prompt 保留独立 Queue adapter,并继续形成不同 turn。更广泛的 [Agent Teams 决策](../feature/2026-08-05-agent-teams.zh.md)继续负责 mailbox、roster、task 与共享 checkout;[相邻 Agent 消息决策](../architecture/2026-08-27-adjacent-agent-steer-messaging.zh.md)继续负责公开 direct-edge authorization 与 model-message source。

+ 2 - 2
.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.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/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md
-2026-07-28-storage-root-and-derived-medium-recovery.md: 1505be1c58d5cf829327b2919113bb2e42798ce7
-2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 1bab5ab663df1419cc826c3d6acb59bd8bff7de0
+2026-07-28-storage-root-and-derived-medium-recovery.md: 68a6792c38a8fe097558de3d5a857ab4ea0d3533
+2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 0c712b3b928f4b3df44e706b154c3a90a784403c

File diff ditekan karena terlalu besar
+ 0 - 1
.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md


+ 1 - 1
.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md

@@ -10,7 +10,7 @@ Status: proposed
 
 **文件到底存在哪(根错位已收口,resolve-once 残余仍开放)。** 共享 base 将会话存储默认为全局 harness home(`$DSH_HOME/sessions`,默认 `~/.dsh/sessions`),而出厂 Web overlay 曾给 json 后端相对根 `./.storages`:`workspace.json` 和 `session_projcache.json` 落在 `<启动目录>/.storages/` 下——从两个不同目录启动,会话相同,工作区注册表和投影缓存却各是一份,而缓存存在的意义恰恰是跨会话冷列表,凡上次在别的启动目录下缓存过的会话全部 miss。这一错位已消除:overlay 现以与会话根同一段 `!!js` 表达式把 `storage-json.root` 锚定到 `$DSH_HOME/storages`(`apps/cli/config/web.cordis.yml`)。残余隐患:`JsonStorageBackend` 仍从不 resolve 根——每次打开 unit 都把路径 join 到当时的 `process.cwd()` 上(packages/storage/storage-json/src/index.ts);出厂 overlay 的根已是绝对路径不受影响,但任何相对根(裸 Loader 启动、测试)仍会被后续 cwd 变化劈开,JSONL 会话后端用「构造时 resolve 一次」防住的正是它("later process.cwd() changes cannot split one backend across roots",packages/session/session-persistence-jsonl/src/index.ts)。
 
-**恢复行为。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者描述的是愿望而非实现。投影缓存半边已被[每会话缓存文件 note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.zh.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
+**恢复行为。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者描述的是愿望而非实现。投影缓存半边已被[每会话缓存文件 note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.zh.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。per-record 投影缓存的 `invalid-record` 一类如今也已被取代:已发布的域声明了 `invalidRecords: 'backup-and-skip'`([跨版本读兼容 note](../../implemented/architecture/2026-09-02-projcache-cross-version-read-compat.zh.md)),open 时把失败记录备份后跳过,因此下文 reset/destroy 提案仅对权威介质或单文档介质的整介质损坏仍然相关。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
 
 ## 提案
 

+ 1 - 1
.github/issue-management/config.json

@@ -5,7 +5,7 @@
   "projectTitle": "DSH Issue Management",
   "lifecycleActor": "dsh-issue-management",
   "priorityField": "Priority",
-  "startDateField": "Start date",
+  "startDateField": "Start Date",
   "projectTimeZone": "Asia/Shanghai",
   "allowUnassignedOwner": true,
   "statuses": [

+ 5 - 2
.github/issue-management/policy.mjs

@@ -484,7 +484,7 @@ async function projectContext(number, includeStatusActor = false, includeStartDa
           title
           fields(first: 50) {
             nodes {
-              ... on ProjectV2Field { id name dataType }
+              ... on ProjectV2Field { id name dataType isIssueField }
               ... on ProjectV2SingleSelectField { id name dataType options { id name } }
             }
           }
@@ -544,6 +544,9 @@ async function projectContext(number, includeStatusActor = false, includeStartDa
   if (startDateField && startDateField.dataType !== 'DATE') {
     throw new Error(`Project ${config.startDateField} 字段必须为 Date`)
   }
+  if (startDateField?.isIssueField) {
+    throw new Error(`Project ${config.startDateField} 字段必须为 Project Date 字段`)
+  }
   const item = issue.projectItems.nodes.find((candidate) => candidate.project.id === project.id)
   const latestStatusEvent = issue.timelineItems?.nodes
     ?.filter((event) => event?.project?.id === project.id)
@@ -582,7 +585,7 @@ async function ensureProjectItem(number, includeStartDate = false) {
 }
 
 /**
- * Initialize one Issue's Project Start date when it is empty.
+ * Initialize one Issue's Project Start Date when it is empty.
  * @param {number} number Same-repository Issue number.
  * @param {string} date Date in YYYY-MM-DD form.
  * @returns {Promise<void>} Resolves after the conditional Project update.

+ 31 - 9
.github/issue-management/policy.test.mjs

@@ -21,6 +21,7 @@ const projectGraphqlData = ({
   startDate = null,
   startDateField = true,
   startDateType = 'DATE',
+  startDateIsIssueField = false,
 } = {}) => ({
   organization: {
     projectV2: {
@@ -30,7 +31,14 @@ const projectGraphqlData = ({
         nodes: [
           { id: 'status-field-id', name: 'Status', dataType: 'SINGLE_SELECT', options: [] },
           ...(startDateField
-            ? [{ id: 'start-date-field-id', name: 'Start date', dataType: startDateType }]
+            ? [
+                {
+                  id: 'start-date-field-id',
+                  name: 'Start Date',
+                  dataType: startDateType,
+                  isIssueField: startDateIsIssueField,
+                },
+              ]
             : []),
         ],
       },
@@ -258,7 +266,7 @@ test('initializes every referenced Issue only for a PR opened event', async () =
   assert.equal(writes.length, 3)
 })
 
-test('writes an empty Project Start date with the configured field', async (t) => {
+test('writes an empty Project Start Date with the configured field', async (t) => {
   const requests = mockGraphql(t, (request) => {
     if (request.query.includes('query(')) return projectGraphqlData()
     return { updateProjectV2ItemFieldValue: { projectV2Item: { id: 'item-id' } } }
@@ -267,6 +275,10 @@ test('writes an empty Project Start date with the configured field', async (t) =
   await initializeIssueStartDate(42, '2026-08-28')
 
   assert.equal(requests.length, 2)
+  assert.match(requests[0].query, /isIssueField/)
+  assert.doesNotMatch(requests[0].query, /issueField\s*\{/)
+  assert.match(requests[0].query, /ProjectV2ItemFieldDateValue/)
+  assert.match(requests[1].query, /updateProjectV2ItemFieldValue/)
   assert.match(requests[1].query, /value: \{date: \$date\}/)
   assert.deepEqual(requests[1].variables, {
     projectId: 'project-id',
@@ -276,7 +288,7 @@ test('writes an empty Project Start date with the configured field', async (t) =
   })
 })
 
-test('preserves an existing Project Start date', async (t) => {
+test('preserves an existing Project Start Date', async (t) => {
   const requests = mockGraphql(t, () => projectGraphqlData({ startDate: '2026-08-01' }))
 
   await initializeIssueStartDate(42, '2026-08-28')
@@ -284,7 +296,7 @@ test('preserves an existing Project Start date', async (t) => {
   assert.equal(requests.length, 1)
 })
 
-test('adds a referenced Issue to the Project before setting Start date', async (t) => {
+test('adds a referenced Issue to the Project before setting Start Date', async (t) => {
   const requests = mockGraphql(t, (request) => {
     if (request.query.includes('query(')) return projectGraphqlData({ projectItem: false })
     if (request.query.includes('addProjectV2ItemById')) {
@@ -297,17 +309,27 @@ test('adds a referenced Issue to the Project before setting Start date', async (
 
   assert.equal(requests.length, 3)
   assert.deepEqual(requests[1].variables, { projectId: 'project-id', contentId: 'issue-id' })
-  assert.equal(requests[2].variables.itemId, 'new-item-id')
+  assert.deepEqual(requests[2].variables, {
+    projectId: 'project-id',
+    itemId: 'new-item-id',
+    fieldId: 'start-date-field-id',
+    date: '2026-08-28',
+  })
 })
 
-test('rejects a missing or non-Date Start date field', async (t) => {
+test('rejects a missing, non-Date, or Issue-level Start Date field', async (t) => {
   let response = projectGraphqlData({ startDateField: false })
   const requests = mockGraphql(t, () => response)
 
-  await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Project 缺少 Start date 字段/)
+  await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Project 缺少 Start Date 字段/)
   response = projectGraphqlData({ startDateType: 'TEXT' })
-  await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start date 字段必须为 Date/)
-  assert.equal(requests.length, 2)
+  await assert.rejects(initializeIssueStartDate(42, '2026-08-28'), /Start Date 字段必须为 Date/)
+  response = projectGraphqlData({ startDateIsIssueField: true })
+  await assert.rejects(
+    initializeIssueStartDate(42, '2026-08-28'),
+    /Start Date 字段必须为 Project Date 字段/,
+  )
+  assert.equal(requests.length, 3)
 })
 
 test('does not treat pull request references as Issue associations', () => {

+ 1 - 1
apps/cli/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh",
   "description": "dsh CLI: profile boot, plugin management, and the browser UI alias",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 6 - 0
apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs

@@ -88,6 +88,9 @@ function implementer(messages) {
   const names = calls(messages)
   const last = latestAssistantCalls(messages)
   const text = latestToolText(messages)
+  const userText = messages.flatMap(message => message.role === 'user'
+    ? message.content.filter(block => block.type === 'text').map(block => block.text)
+    : []).join('\n')
   if (!names.includes('team_task_create')) {
     if (last.includes('team_task_get') && text.includes('"subject":"Research"')) {
       return toolChunks([{ name: 'team_task_create', args: {
@@ -116,6 +119,9 @@ function implementer(messages) {
     }
     return toolChunks([{ name: 'team_task_get', args: { task_id: 'task-1' } }])
   }
+  if (!userText.includes('Research complete: use the deterministic finding.')) {
+    return toolChunks([{ name: 'wait_agent', args: { timeout_ms: 10000 } }])
+  }
   if (!names.includes('send_message')) {
     return toolChunks([
       { name: 'team_task_update', args: { task_id: 'task-2', expected_revision: 2, action: 'complete' } },

+ 55 - 0
apps/cli/tests/profiles/headless/tests/headless.expected.e2e.ts

@@ -558,6 +558,49 @@ describe('headless stream-json snapshots', () => {
         const tasks = rows.filter(row => row.type === 'team/task')
           .map(row => ((row.data as JsonObject).task as JsonObject))
         const latestTasks = Object.values(Object.fromEntries(tasks.map(task => [String(task.subject), task])))
+        const implementer = logs.find(log => typeof log.header.parentSession === 'string'
+          && parseJsonl(log.content).some((row) => {
+            if (row.type !== 'user/message') return false
+            const content: unknown = (row.data as JsonObject).content
+            return Array.isArray(content) && content.some((block: unknown) => (
+              typeof block === 'object' && block !== null && !Array.isArray(block)
+              && (block as JsonObject).type === 'text'
+              && typeof (block as JsonObject).text === 'string'
+              && ((block as JsonObject).text as string).includes('IMPLEMENTER_MARK')
+            ))
+          }))
+        if (implementer === undefined) throw new Error('Agent Teams snapshot did not persist the implementer')
+        const implementerRows = parseJsonl(implementer.content)
+        const steeredInboxIndex = implementerRows.findIndex((row) => {
+          if (row.type !== 'agent/inbox/spliced') return false
+          const data = row.data as JsonObject
+          const inserted: unknown = data.inserted
+          return data.target === 'next-step' && Array.isArray(inserted)
+            && inserted.some((message: unknown) => {
+              if (typeof message !== 'object' || message === null || Array.isArray(message)) return false
+              const source = (message as JsonObject).source
+              return typeof source === 'object' && source !== null && !Array.isArray(source)
+                && (source as JsonObject).kind === 'team-message'
+            })
+        })
+        const steeredMessageIndex = implementerRows.findIndex((row) => {
+          if (row.type !== 'user/message') return false
+          const source = (row.data as JsonObject).source
+          return typeof source === 'object' && source !== null && !Array.isArray(source)
+            && (source as JsonObject).kind === 'team-message'
+        })
+        const openTurnStart = implementerRows.findLastIndex((row, index) => (
+          index < steeredMessageIndex && row.type === 'turn/start'
+        ))
+        const openTurnEnd = implementerRows.findLastIndex((row, index) => (
+          index < steeredMessageIndex && row.type === 'turn/end'
+        ))
+        const completionAfterSteer = implementerRows.some((row, index) => {
+          if (index <= steeredMessageIndex || row.type !== 'tool/call') return false
+          const data = row.data as JsonObject
+          if (data.name !== 'team_task_update' || typeof data.arguments !== 'string') return false
+          return (JSON.parse(data.arguments) as JsonObject).action === 'complete'
+        })
         projection = {
           sessions: logs.length,
           memberEdges: members.length,
@@ -573,6 +616,12 @@ describe('headless stream-json snapshots', () => {
             && (row.data as JsonObject).name === 'wait_agent'),
           checkedRoster: rows.some(row => row.type === 'tool/call'
             && (row.data as JsonObject).name === 'list_agents'),
+          steerEvidence: {
+            nextStepInbox: steeredInboxIndex >= 0,
+            messageEntered: steeredMessageIndex > steeredInboxIndex,
+            enteredOpenTurn: openTurnStart > openTurnEnd,
+            completedAfterMessage: completionAfterSteer,
+          },
         }
       },
     })
@@ -592,6 +641,12 @@ describe('headless stream-json snapshots', () => {
         "memberEdges": 4,
         "queuedMessages": 2,
         "sessions": 3,
+        "steerEvidence": {
+          "completedAfterMessage": true,
+          "enteredOpenTurn": true,
+          "messageEntered": true,
+          "nextStepInbox": true,
+        },
         "tasks": [
           {
             "revision": 3,

+ 1 - 1
apps/web/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-web-frontend",
   "description": "Web application entry: vite build over the @deepseek-ai/dsh-client-web shell library; dist/ served by apps/cli's dsh web",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 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: 07e0badaaa832e7d12f5a12a91ff1a17539c448b
-config-catalog.zh.md: 66335c5de5b87ccbcdab7ffa64ff366e1c677ba0
+config-catalog.md: 161bf878a0541a165c642f405996e9cee6d8c214
+config-catalog.zh.md: 095d4336469333599958169c46e9e2e345a05076

+ 1 - 1
docs/config-catalog.md

@@ -537,7 +537,7 @@ export interface Config {
 }
 ```
 
-Source: [`packages/experimental/agent-team/src/types.ts:131`](../packages/experimental/agent-team/src/types.ts)
+Source: [`packages/experimental/agent-team/src/types.ts:130`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="deepseek-aidsh-experimental-code-runtime-python"></a>
 

+ 1 - 1
docs/config-catalog.zh.md

@@ -539,7 +539,7 @@ export interface Config {
 }
 ```
 
-来源:[`packages/experimental/agent-team/src/types.ts:125`](../packages/experimental/agent-team/src/types.ts)
+来源:[`packages/experimental/agent-team/src/types.ts:124`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="deepseek-aidsh-experimental-code-runtime-python"></a>
 

+ 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: 1439f5db933899cd145e302d9eefcebfad94097b
-persistence-catalog.zh.md: 1c18a63f4382edafd23cf6c70dbef8922add34d3
+persistence-catalog.md: 1c0c6919987c691b82c4639aff0f779c95dca83c
+persistence-catalog.zh.md: 4cc8ba5b7fc76708a80285013ebcbb03fe3e8e4a

+ 8 - 8
docs/persistence-catalog.md

@@ -776,12 +776,12 @@ Source: [`packages/subagent/tool-subagent/src/model-selection-state.ts:17`](../p
 
 ```ts persistence-catalog
 /** Whole teammate lifecycle value, stored only in the Team Lead Session. */
-'team/member': { version: 1; teamId: TeamId; member: TeamMemberSnapshot }
+'team/member': { version: 2; teamId: TeamId; member: TeamMemberSnapshot }
 ```
 
 Types: [TeamId](subsystems/agent-team.md) · [TeamMemberSnapshot](subsystems/agent-team.md)
 
-Source: [`packages/experimental/agent-team/src/types.ts:223`](../packages/experimental/agent-team/src/types.ts)
+Source: [`packages/experimental/agent-team/src/types.ts:221`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teammessagedelivered--log-only"></a>
 
@@ -790,7 +790,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:223`](../packages/experi
 ```ts persistence-catalog
 /** Durable acknowledgement that the target Session recorded the message. */
 'team/message/delivered': {
-  version: 1
+  version: 2
   teamId: TeamId
   messageId: TeamMessageId
   targetId: SessionId
@@ -799,7 +799,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:223`](../packages/experi
 
 Types: [TeamId](subsystems/agent-team.md) · [TeamMessageId](subsystems/agent-team.md)
 
-Source: [`packages/experimental/agent-team/src/types.ts:229`](../packages/experimental/agent-team/src/types.ts)
+Source: [`packages/experimental/agent-team/src/types.ts:227`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teammessagequeued--log-only"></a>
 
@@ -807,12 +807,12 @@ Source: [`packages/experimental/agent-team/src/types.ts:229`](../packages/experi
 
 ```ts persistence-catalog
 /** Durable mailbox enqueue, stored before delivery is attempted. */
-'team/message/queued': { version: 1; teamId: TeamId; message: TeamMessageSnapshot }
+'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot }
 ```
 
 Types: [TeamId](subsystems/agent-team.md) · [TeamMessageSnapshot](subsystems/agent-team.md)
 
-Source: [`packages/experimental/agent-team/src/types.ts:227`](../packages/experimental/agent-team/src/types.ts)
+Source: [`packages/experimental/agent-team/src/types.ts:225`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teamtask--log-only"></a>
 
@@ -820,12 +820,12 @@ Source: [`packages/experimental/agent-team/src/types.ts:227`](../packages/experi
 
 ```ts persistence-catalog
 /** Whole shared-task value, stored only in the Team Lead Session. */
-'team/task': { version: 1; teamId: TeamId; task: TeamTaskSnapshot }
+'team/task': { version: 2; teamId: TeamId; task: TeamTaskSnapshot }
 ```
 
 Types: [TeamId](subsystems/agent-team.md) · [TeamTaskSnapshot](subsystems/agent-team.md)
 
-Source: [`packages/experimental/agent-team/src/types.ts:225`](../packages/experimental/agent-team/src/types.ts)
+Source: [`packages/experimental/agent-team/src/types.ts:223`](../packages/experimental/agent-team/src/types.ts)
 
 ### `todo/*`
 

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

@@ -778,12 +778,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 
 ```ts persistence-catalog
 /** Whole teammate lifecycle value, stored only in the Team Lead Session. */
-'team/member': { version: 1; teamId: TeamId; member: TeamMemberSnapshot }
+'team/member': { version: 2; teamId: TeamId; member: TeamMemberSnapshot }
 ```
 
 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMemberSnapshot](subsystems/agent-team.zh.md)
 
-来源:[`packages/experimental/agent-team/src/types.ts:206`](../packages/experimental/agent-team/src/types.ts)
+来源:[`packages/experimental/agent-team/src/types.ts:204`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teammessagedelivered--log-only"></a>
 
@@ -792,7 +792,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 ```ts persistence-catalog
 /** Durable acknowledgement that the target Session recorded the message. */
 'team/message/delivered': {
-  version: 1
+  version: 2
   teamId: TeamId
   messageId: TeamMessageId
   targetId: SessionId
@@ -801,7 +801,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 
 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageId](subsystems/agent-team.zh.md)
 
-来源:[`packages/experimental/agent-team/src/types.ts:212`](../packages/experimental/agent-team/src/types.ts)
+来源:[`packages/experimental/agent-team/src/types.ts:210`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teammessagequeued--log-only"></a>
 
@@ -809,12 +809,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 
 ```ts persistence-catalog
 /** Durable mailbox enqueue, stored before delivery is attempted. */
-'team/message/queued': { version: 1; teamId: TeamId; message: TeamMessageSnapshot }
+'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot }
 ```
 
 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageSnapshot](subsystems/agent-team.zh.md)
 
-来源:[`packages/experimental/agent-team/src/types.ts:210`](../packages/experimental/agent-team/src/types.ts)
+来源:[`packages/experimental/agent-team/src/types.ts:208`](../packages/experimental/agent-team/src/types.ts)
 
 <a id="teamtask--log-only"></a>
 
@@ -822,12 +822,12 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
 
 ```ts persistence-catalog
 /** Whole shared-task value, stored only in the Team Lead Session. */
-'team/task': { version: 1; teamId: TeamId; task: TeamTaskSnapshot }
+'team/task': { version: 2; teamId: TeamId; task: TeamTaskSnapshot }
 ```
 
 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamTaskSnapshot](subsystems/agent-team.zh.md)
 
-来源:[`packages/experimental/agent-team/src/types.ts:208`](../packages/experimental/agent-team/src/types.ts)
+来源:[`packages/experimental/agent-team/src/types.ts:206`](../packages/experimental/agent-team/src/types.ts)
 
 ### `todo/*`
 

+ 2 - 2
docs/subsystems/agent-team.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/agent-team.md
-agent-team.md: e3a746734b067bb7ef8f083c5c9af9cb3707dcf7
-agent-team.zh.md: 1f885a73861c091b50d27d15080ebc09f6d382ec
+agent-team.md: 201123ea94499ff20cae25d68b1f7a826439d143
+agent-team.zh.md: d6973ae6899c1d884dc51bd71afbd36a41895527

+ 4 - 3
docs/subsystems/agent-team.md

@@ -2,7 +2,7 @@
 
 English | [中文](agent-team.zh.md)
 
-Types shared by the experimental implicit-root Team domain, model tools, and host adapters. The [Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md) owns identity, mailbox, task, and shared-checkout decisions; this page records the literal durable forms from [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts).
+Types shared by the experimental implicit-root Team domain, model tools, and host adapters. The [Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.md) owns identity, mailbox, task, and shared-checkout decisions; the [Team Steer messaging Agent Note](../../.agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.md) owns message scheduling; this page records the literal durable forms from [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts).
 
 ## Identity and roster
 
@@ -34,11 +34,12 @@ interface TeamMessageSnapshot {
   readonly senderId: SessionId
   readonly senderName: string
   readonly targetId: SessionId
-  readonly delivery: 'quiet' | 'wakeup'
   readonly content: ContentBlock[]
 }
 ```
 
+Every message attempts Steer delivery. A running target receives it at the nearest step boundary, an idle target starts a turn, and an inactive teammate cold-resumes. Scheduling is not stored in the durable record because callers cannot select another mode.
+
 The target Session keeps message identity and sender attribution on both the pending inbox item and the eventual user message. Folding that source across inbox and history is the target-side de-duplication key; the model-visible framing repeats the id and sender.
 
 ```ts type-equiv
@@ -116,7 +117,7 @@ async spawnTeammate(caller: Agent, request: SpawnTeammateRequest): Promise<Spawn
 /**
  * Queue one durable peer message, then attempt immediate delivery.
  * @param caller - exact live sending Team member.
- * @param request - target name, content, scheduling mode, and pre-queue cancellation.
+ * @param request - target name, content, and pre-queue cancellation.
  * @returns durable message identity and immediate-delivery observation.
  */
 async sendMessage(caller: Agent, request: SendTeamMessageRequest): Promise<SendTeamMessageResult>

+ 4 - 3
docs/subsystems/agent-team.zh.md

@@ -2,7 +2,7 @@
 
 [English](agent-team.md) | 中文
 
-实验性隐式 Root Team 领域、模型工具与宿主适配器共享的类型。[Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md)负责身份、mailbox、task 与共享 checkout 决策;本页记录 [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts) 中的字面持久形式。
+实验性隐式 Root Team 领域、模型工具与宿主适配器共享的类型。[Agent Teams Agent Note](../../.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md)负责身份、mailbox、task 与共享 checkout 决策;[Team Steer 消息 Agent Note](../../.agents/notes/implemented/simplification/2026-08-30-team-send-message-steer.zh.md)负责消息调度;本页记录 [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts) 中的字面持久形式。
 
 ## 身份与 roster
 
@@ -34,11 +34,12 @@ interface TeamMessageSnapshot {
   readonly senderId: SessionId
   readonly senderName: string
   readonly targetId: SessionId
-  readonly delivery: 'quiet' | 'wakeup'
   readonly content: ContentBlock[]
 }
 ```
 
+每条消息都会尝试 Steer 投递。running target 在最近的步骤边界收到消息,idle target 启动一个轮次,inactive teammate 则冷恢复。调用方不能选择其他模式,因此持久记录不存储调度方式。
+
 target Session 会在 pending inbox 条目和最终用户消息上保留消息身份与发送者归因。跨 inbox 与历史折叠该 source 构成 target 侧去重键;模型可见的 framing 会重复 id 和发送者。
 
 ```ts type-equiv
@@ -116,7 +117,7 @@ async spawnTeammate(caller: Agent, request: SpawnTeammateRequest): Promise<Spawn
 /**
  * Queue one durable peer message, then attempt immediate delivery.
  * @param caller - exact live sending Team member.
- * @param request - target name, content, scheduling mode, and pre-queue cancellation.
+ * @param request - target name, content, and pre-queue cancellation.
  * @returns durable message identity and immediate-delivery observation.
  */
 async sendMessage(caller: Agent, request: SendTeamMessageRequest): Promise<SendTeamMessageResult>

+ 2 - 2
docs/subsystems/storage.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/storage.md
-storage.md: 1e4141e6ef1c6f8e1c2593e21e788b626d6b1ed7
-storage.zh.md: f0433c600674741c3de0ce3e99430297839ce124
+storage.md: 03b0fa1c674064994039d37c4fec906dc45794b3
+storage.zh.md: ded8783fbfe7f0d7d8a846c7a66d780b48de1445

+ 28 - 5
docs/subsystems/storage.md

@@ -44,7 +44,7 @@ interface StorageBackend {
 }
 ```
 
-A backend owns one medium (a file-tree root, a database file) and exposes optional operation groups; `kv` is the only shipped group. `KvFacet.open(descriptor)` opens one named unit — `KvUnitDescriptor` carries the name, format version, table names, and whether a global singleton slot exists — and returns a `KvUnit` with `loadAll`, `putRecord`, `deleteRecord`, `setGlobal`, and `close`. Unit and table names must match `UNIT_NAME_RE` (safe as a file name and as a SQL identifier segment); record keys are arbitrary strings that never reach file paths. A unit does not serialize concurrent writes — ordering belongs to the caller — but each single call is atomic on the medium and durable once resolved. A medium stamped with a different version rejects `version-mismatch`; one that cannot be parsed as the unit rejects `malformed-medium` (no migration, pre-release stance). [`backend.ts`](../../packages/storage/storage/src/backend.ts) is the normative clause-by-clause contract, and the shared conformance suite in [`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) checks every clause against each backend. The [json backend](../../packages/storage/storage-json/README.md) republishes one whole human-readable file per unit atomically; the [sqlite backend](../../packages/storage/storage-sqlite/README.md) stores one document per row in one database for frequently updated data.
+A backend owns one medium (a file-tree root, a database file) and exposes optional operation groups; `kv` is the only shipped group. `KvFacet.open(descriptor)` opens one named unit — `KvUnitDescriptor` carries the name, current format version, optional compatible record versions, table names, and whether a global singleton slot exists — and returns a `KvUnit` with `loadAll`, `putRecord`, `deleteRecord`, `setGlobal`, and `close`. Unit and table names must match `UNIT_NAME_RE` (safe as a file name and as a SQL identifier segment); record keys are arbitrary strings that never reach file paths. A unit does not serialize concurrent writes — ordering belongs to the caller — but each single call is atomic on the medium and durable once resolved. A `single` medium stamped with a different version rejects `version-mismatch`; a `per-record` document stamped outside the accepted set reads as absent. A medium that cannot be parsed as the unit rejects `malformed-medium`. [`backend.ts`](../../packages/storage/storage/src/backend.ts) is the normative clause-by-clause contract, and the shared conformance suite in [`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) checks every clause against each backend. The [json backend](../../packages/storage/storage-json/README.md) republishes one whole human-readable file per unit atomically; the [sqlite backend](../../packages/storage/storage-sqlite/README.md) stores one document per row in one database for frequently updated data.
 
 ## Declaring a domain
 
@@ -55,16 +55,36 @@ A domain is declared once by its owning package as a spec object — the single
 interface DomainSpec {
   /** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */
   readonly name: string
-  /** Domain format version; a medium stamped with a different version rejects at open. */
+  /** Current domain format version; reads enforce it according to the selected layout. */
   readonly version: number
   /**
    * Medium layout for the backend unit: `single` (the default) stores the
    * whole unit as one document; `per-record` stores each record as its own
    * document, for units whose records are large, sparse, or individually
-   * disposable — the projection cache — and scopes version bumps per record
-   * (a stale record document is discarded, never migrated).
+   * disposable — the projection cache — and scopes version checks per record
+   * (an unaccepted record document is discarded, never migrated).
    */
   readonly layout?: 'single' | 'per-record'
+  /**
+   * Older domain versions whose stored records the current record schemas
+   * also accept (the declaring owner vouches for that, typically by
+   * declaring the fields older records lack as optional). `per-record` backends
+   * read documents stamped with a listed version instead of discarding them,
+   * and accept a legacy whole-unit file so stamped for the one-time
+   * bootstrap; writes always stamp {@link version}.
+   */
+  readonly compatibleVersions?: readonly number[]
+  /**
+   * What `open` does with a stored table record that fails its zod schema.
+   * Absent (the default), the whole open rejects with `invalid-record` —
+   * right for authoritative data. `'backup-and-skip'` is for domains whose
+   * records are disposable derived data: the backend moves the record's
+   * document aside (`KvUnit.backupRecord`), the failure is logged with
+   * its cause, and the open continues with the record absent. A backend
+   * without `backupRecord` (no per-record document to move) falls back
+   * to the rejecting default. The global slot always rejects.
+   */
+  readonly invalidRecords?: 'backup-and-skip'
   /** Optional global singleton slot. */
   readonly global?: DomainGlobalSpec<unknown>
   /** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */
@@ -180,7 +200,10 @@ The mounted domain facility. Opens declared domains over routed backends; one fa
  * (`facet-unsupported`); open the unit projected from the spec (backend
  * `version-mismatch`/`malformed-medium` pass through); load and validate
  * every stored record against the spec's zod schemas (`invalid-record`
- * with the offending table and key); construct the domain.
+ * with the offending table and key — unless the spec declares
+ * `invalidRecords: 'backup-and-skip'` and the unit can move documents aside, in
+ * which case the failing record is backed up, logged, and skipped);
+ * construct the domain.
  *
  * Lifecycle: the CALLER owns the returned handle and closes it via
  * `Domain.close()` (typically as its own `ctx.effect` disposer) — the

+ 28 - 5
docs/subsystems/storage.zh.md

@@ -44,7 +44,7 @@ interface StorageBackend {
 }
 ```
 
-一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;`kv` 是唯一已交付的操作组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、格式版本、表名清单,以及是否存在全局单例 slot——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。介质上记录的版本与之不同时拒绝 `version-mismatch`;无法按该 unit 解析的介质拒绝 `malformed-medium`(不做迁移:预发布立场)。[`backend.ts`](../../packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](../../packages/storage/storage-json/README.zh.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](../../packages/storage/storage-sqlite/README.zh.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
+一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;`kv` 是唯一已交付的操作组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、当前格式版本、可选的兼容记录版本、表名清单,以及是否存在全局单例 slot——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。`single` 介质上记录的版本不同时拒绝 `version-mismatch`;`per-record` 文档的版本在接受集合之外时读作不存在。无法按该 unit 解析的介质拒绝 `malformed-medium`。[`backend.ts`](../../packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](../../packages/storage/storage-json/README.zh.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](../../packages/storage/storage-sqlite/README.zh.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
 
 ## 声明领域
 
@@ -55,16 +55,36 @@ interface StorageBackend {
 interface DomainSpec {
   /** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */
   readonly name: string
-  /** Domain format version; a medium stamped with a different version rejects at open. */
+  /** Current domain format version; reads enforce it according to the selected layout. */
   readonly version: number
   /**
    * Medium layout for the backend unit: `single` (the default) stores the
    * whole unit as one document; `per-record` stores each record as its own
    * document, for units whose records are large, sparse, or individually
-   * disposable — the projection cache — and scopes version bumps per record
-   * (a stale record document is discarded, never migrated).
+   * disposable — the projection cache — and scopes version checks per record
+   * (an unaccepted record document is discarded, never migrated).
    */
   readonly layout?: 'single' | 'per-record'
+  /**
+   * Older domain versions whose stored records the current record schemas
+   * also accept (the declaring owner vouches for that, typically by
+   * declaring the fields older records lack as optional). `per-record` backends
+   * read documents stamped with a listed version instead of discarding them,
+   * and accept a legacy whole-unit file so stamped for the one-time
+   * bootstrap; writes always stamp {@link version}.
+   */
+  readonly compatibleVersions?: readonly number[]
+  /**
+   * What `open` does with a stored table record that fails its zod schema.
+   * Absent (the default), the whole open rejects with `invalid-record` —
+   * right for authoritative data. `'backup-and-skip'` is for domains whose
+   * records are disposable derived data: the backend moves the record's
+   * document aside (`KvUnit.backupRecord`), the failure is logged with
+   * its cause, and the open continues with the record absent. A backend
+   * without `backupRecord` (no per-record document to move) falls back
+   * to the rejecting default. The global slot always rejects.
+   */
+  readonly invalidRecords?: 'backup-and-skip'
   /** Optional global singleton slot. */
   readonly global?: DomainGlobalSpec<unknown>
   /** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */
@@ -180,7 +200,10 @@ The mounted domain facility. Opens declared domains over routed backends; one fa
  * (`facet-unsupported`); open the unit projected from the spec (backend
  * `version-mismatch`/`malformed-medium` pass through); load and validate
  * every stored record against the spec's zod schemas (`invalid-record`
- * with the offending table and key); construct the domain.
+ * with the offending table and key — unless the spec declares
+ * `invalidRecords: 'backup-and-skip'` and the unit can move documents aside, in
+ * which case the failing record is backed up, logged, and skipped);
+ * construct the domain.
  *
  * Lifecycle: the CALLER owns the returned handle and closes it via
  * `Domain.close()` (typically as its own `ctx.effect` disposer) — the

+ 2 - 2
docs/tool-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/tool-catalog.md
-tool-catalog.md: f71c5dea97ca92fd816c45c6fbd816ee05309d63
-tool-catalog.zh.md: 3286da7015cfa793820f6107a96af1fc73472ddb
+tool-catalog.md: 416585106de28296a3105fafbbe6e4edc0d0f16b
+tool-catalog.zh.md: 89d2e467863c09b78805fd9db8ec785ad7936a9d

+ 3 - 29
docs/tool-catalog.md

@@ -36,7 +36,7 @@ This table connects model-visible tool names to the plugin package and service s
 | `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`, `subagent` | `ctx.tools`, `ctx.subagents`, `ctx.systemPrompt`, `ctx.llm for model discovery and selected-route validation` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance independently controls whether it reads model-selection settings and its background behavior through `modelSelectionSettings`, `backgroundMode`, and `enableRunInBackground`. |
 | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`, `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named control tools over continuable background subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). |
 | `@deepseek-ai/dsh-tool-jobs` | `job_kill`, `job_list`, `job_output` | `ctx.tools`, `ctx.jobs`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-job controller: background bash commands, PTY sends, and subagents are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. |
-| `@deepseek-ai/dsh-experimental-tool-agent-team` | `followup_task`, `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All ten tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. |
+| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. |
 | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. |
 | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`, `ctx.workflowEngine`, `ctx.systemPrompt`, `a calling Agent (exec.agent parents the script children)` | `tool/call`, `tool/result` | - | - |
 | `@deepseek-ai/dsh-tool-web` | `web_fetch`, `web_search` | `ctx.tools`, `ctx.web`, `ctx.systemPrompt` | `tool/call`, `tool/result` | - | web_search and web_fetch keep provider selection behind ctx.web so model-visible schemas stay stable across backend swaps. |
@@ -1742,32 +1742,6 @@ The kind-agnostic background-job controller: background bash commands, PTY sends
 
 ## `@deepseek-ai/dsh-experimental-tool-agent-team`
 
-### `followup_task`
-
-Send a durable follow-up task to another Team member and start a turn when needed.
-
-```json
-{
-  "type": "object",
-  "properties": {
-    "target": {
-      "type": "string",
-      "description": "Team member name, or lead."
-    },
-    "message": {
-      "type": "string",
-      "description": "Self-contained message for the target."
-    }
-  },
-  "required": [
-    "target",
-    "message"
-  ]
-}
-```
-
-Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts)
-
 ### `interrupt_agent`
 
 Interrupt one teammate's current turn while preserving its pending inbox. Team Lead only.
@@ -1804,7 +1778,7 @@ Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/exper
 
 ### `send_message`
 
-Send durable information to another Team member without starting an idle member.
+Send one durable message to another Team member. A running target receives it at the nearest step boundary; an idle target starts a turn; an inactive teammate cold-resumes.
 
 ```json
 {
@@ -2052,7 +2026,7 @@ Wait for the next teammate status, mailbox, or shared-task change after this cal
 
 Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts)
 
-All ten tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names.
+All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names.
 
 <a id="deepseek-aidsh-tool-todo"></a>
 

+ 2 - 28
docs/tool-catalog.zh.md

@@ -40,7 +40,7 @@
 | `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`、`subagent` | `ctx.tools`、`ctx.subagents`、`ctx.systemPrompt`、`用于模型发现和所选路由校验的 ctx.llm` | `tool/call`、`tool/result`、`child session events through the chosen provider` | `subagent`、`subagent_fork` | 注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `modelSelectionSettings`、`backgroundMode` 与 `enableRunInBackground` 独立控制是否读取模型选择设置及其后台行为。 |
 | `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`、`list_agents`、`send_message` | `ctx.tools`、`ctx.subagents`、`ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`、`tool/result`、`child session events through ctx.subagents` | - | 这些是控制可继续后台 subagent 的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 |
 | `@deepseek-ai/dsh-tool-jobs` | `job_kill`、`job_list`、`job_output` | `ctx.tools`、`ctx.jobs`、`ctx.systemPrompt` | `tool/call`、`tool/result`、`user/message via agent.inject() for background completion notices` | - | 与任务种类无关的后台任务控制器:后台 bash 命令、PTY 发送和 subagent 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 |
-| `@deepseek-ai/dsh-experimental-tool-agent-team` | `followup_task`、`interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 10 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 |
+| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 9 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 |
 | `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`、`owning Agent session` | `tool/call`、`todo/write`、`tool/result` | - | todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 |
 | `@deepseek-ai/dsh-tool-workflow` | `workflow` | `ctx.tools`、`ctx.workflowEngine`、`ctx.systemPrompt`、`a calling Agent (exec.agent parents the script children)` | `tool/call`、`tool/result` | - | - |
 | `@deepseek-ai/dsh-tool-web` | `web_fetch`、`web_search` | `ctx.tools`、`ctx.web`、`ctx.systemPrompt` | `tool/call`、`tool/result` | - | web_search 和 web_fetch 将提供方选择置于 ctx.web 之后,使模型可见 schema 在更换后端时保持稳定。 |
@@ -1748,32 +1748,6 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
 
 ## `@deepseek-ai/dsh-experimental-tool-agent-team`
 
-### `followup_task`
-
-向另一名 Team member 发送持久 follow-up task,并在需要时启动一个 turn。
-
-```json
-{
-  "type": "object",
-  "properties": {
-    "target": {
-      "type": "string",
-      "description": "Team member name, or lead."
-    },
-    "message": {
-      "type": "string",
-      "description": "Self-contained message for the target."
-    }
-  },
-  "required": [
-    "target",
-    "message"
-  ]
-}
-```
-
-来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts)
-
 ### `interrupt_agent`
 
 中断一名 teammate 的当前 turn,同时保留其待处理 inbox。仅 Team Lead 可用。
@@ -1810,7 +1784,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
 
 ### `send_message`
 
-向另一名 Team member 发送持久信息,但不启动 idle member
+向另一名 Team member 发送一条持久消息。running target 会在最近的步骤边界收到消息;idle target 会启动一个 turn;inactive teammate 会冷恢复
 
 ```json
 {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@deepseek-ai/dsh-root",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "license": "MIT",
   "private": true,
   "type": "module",

+ 1 - 1
packages/acp/acp/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-acp",
   "description": "Automation-only Agent Client Protocol server for driving DeepSeek Harness agents over JSON-RPC stdio",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/api/gateway/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-api-gateway",
   "description": "Typert Remote Host dispatcher and Client API endpoint",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/api/remotes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-api-remotes",
   "description": "Remote BFF assembly for application-selected Host capabilities",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/api/session-controller/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-api-session-controller",
   "description": "Session Remote commands, cold reads, and live control transport",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/api/settings-controller/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-api-settings-controller",
   "description": "Remote owner for the configuration surfaces over the settings-domain seams",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/api/workspace-controller/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-api-workspace-controller",
   "description": "Workspace Remote commands and reconnect-safe state transport",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/attachment/attachment-local/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-attachment-local",
   "description": "Private content-addressed DSH_HOME attachment storage",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/attachment/attachment/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-attachment",
   "description": "Durable immutable attachment storage seam for the DeepSeek Harness",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/boot/app-boot/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-app-boot",
   "description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/boot/cmdline/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-cmdline",
   "description": "Immutable command-line handoff from a dsh launcher to any app plugin that injects cmdlineArgs",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/acp-app/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-acp-app",
   "description": "The dsh ACP profile bundle: automation-only JSON-RPC stdio and process lifecycle over dsh-base",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/base/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-base",
   "description": "The shared dsh core as a profile bundle: the first patch layer of base-backed profiles, inserting core rows over the empty profile root",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/headless/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-headless",
   "description": "The dsh one-shot bundle: a direct core Agent/Session runner over dsh-base with no Host, HTTP, or browser layer",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/sdk-app/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-sdk-app",
   "description": "The dsh SDK profile bundle: stdio JSON-RPC serving and process lifecycle over dsh-base",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/sdk-minimal/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-sdk-minimal",
   "description": "The standalone minimal SDK profile bundle: JSON-RPC, one DeepSeek adapter, persistent shell, editor, and JSONL sessions",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/bundle/web-app/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-web-app",
   "description": "The dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/connection/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-connection",
   "description": "Authenticated RPC transport, generation lifecycle, and browser fixture",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/hmr/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-hmr",
   "description": "Dev-only hot-reload driver for script-loaded client entries: SSE rebuilt frames → invalidate/prefetch → fiber swap through the vendored Loader entry",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/locale/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-locale",
   "description": "Locale plugin: Host-backed preference, extensible language catalog, browser fallback, and typed built-in dictionaries",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/modules/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-modules",
   "description": "Client module system, dual-face: node half composes the __DSH_BOOT__ entry graph (incremental dsh.client scan, bundle route, index tap, webPlugins service); browser half is the lazy-CJS module table the vendored cordis Loader consumes as its internal seam",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/store/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-store",
   "description": "React-free observable and snapshot-store contracts with the shared Zustand/Immer engine",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-agent-preset/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-agent-preset",
   "description": "Agent-preset surfaces: the default for later sessions, this session's seat, and the composition editor",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-approval/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-approval",
   "description": "Approval composer takeover over the scoped Remote Event waterfall",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-attachment/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-attachment",
   "description": "Dynamic attachment presentation plugin for conversation input, message-image, and trajectory image slots",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-brand-official/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-brand-official",
   "description": "Official DeepSeek Harness brand occupants for the Web client's sidebar slots",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-chat/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-chat",
   "description": "Chat Conversation target, node definitions, renderers, and details surface",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-commands/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-commands",
   "description": "Client command surface: global directory cache, '/' source, three command UI kinds, popupSelect registry",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-conversation/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-conversation",
   "description": "Target-neutral Conversation assembly, shell, composer, queue, and view navigation",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-deliverables/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-deliverables",
   "description": "Produced-files turn tail and clickable final-response file references for Web",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-directory-picker-browse/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-directory-picker-browse",
   "description": "In-app directory browsing surface: the workspace directory-flow owner rendering the host's listing and creation primitives",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-directory-picker-native/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-directory-picker-native",
   "description": "Native directory-picker surface: the renderless workspace directory-flow occupant driving the host's OS chooser",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-goal/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-goal",
   "description": "Session goal surface: GoalBar docked above the composer, read from the goal session projection",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-input-trigger/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-input-trigger",
   "description": "Input trigger pipeline: '/' and '@' detection, candidate menu, pick routing to registered sources",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-jobs/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-jobs",
   "description": "Session-header background-job list: live registry state mirrored from session/jobs frames",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "type": "module",
   "main": "lib/index.js",
   "types": "lib/types/index.d.ts",

+ 1 - 1
packages/client/ui-layout/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-layout",
   "description": "Shell plugin: three-column AppFrame with drag handles, ctx.layout viewing-state service (navigation + panels)",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-message-feedback/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-message-feedback",
   "description": "Per-message feedback controls contributed to the assistant-message action strip, backed by the messageFeedback Host Remote",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-model-selection/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-model-selection",
   "description": "Model selection over the shared model catalog, Session projection, and session.selectModel",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-permission-presets/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-permission-presets",
   "description": "Permission surfaces: a new-session default in General settings and a current-session /permission popup over the permissions projection",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-plan/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-plan",
   "description": "Plan-mode composer control: the conversation.input.plan seat over the plan projection and the /plan command channel",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-primitives/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-primitives",
   "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-reference/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-reference",
   "description": "Unified Web @file and @session reference source",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-renderer/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-renderer",
   "description": "Browser UI renderer: React slot bindings, ctx.uiRenderer, and the assembled application root",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-schedule/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-schedule",
   "description": "Read-only active Schedule catalog in the Web Session header",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "type": "module",
   "main": "lib/index.js",
   "types": "lib/types/index.d.ts",

+ 1 - 1
packages/client/ui-session/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-session",
   "description": "Session Controller adapter for React and session-scoped slots",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-settings-general/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-settings-general",
   "description": "Settings ownerless-copy and product onboarding plugin: the General section, shell trigger/header chrome content, settings dictionaries, and the versioned welcome notice",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-settings-models/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-settings-models",
   "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-settings-plugin-inventory/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-settings-plugin-inventory",
   "description": "Read-only Cordis Loader inventory tab in Web Plugins settings",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-settings-plugins/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-settings-plugins",
   "description": "Plugins settings section with feature-owned tabs and configurable host-plane plugin cards",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-settings/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-settings",
   "description": "Settings domain base plugin: the settings-namespace scope service and the canonical settings slot-type contract",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-sidebar/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-sidebar",
   "description": "Sidebar plugin: session multi-level tree, search, grouping, state dots",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-skill/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-skill",
   "description": "Web skill references and the dedicated skill tool row",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-slots/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-slots",
   "description": "Slot registry pure core: SlotMap declaration merging, single register composition API, four-share props types, store-seat types, renderer install seam",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-subagent/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-subagent",
   "description": "Subagent conversation catalog, continuation routing UI, and '@' reference source",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-theme/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-theme",
   "description": "Theme plugin: Host bootstrap for the pre-plugin palette; DOM-free ThemeRuntime for light/dark/system state; --dsw-* token styles and Appearance settings row",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-tool/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-tool",
   "description": "Client Tool call-tree renderer and keyed per-tool presentation slot",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

+ 1 - 1
packages/client/ui-trajectory/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@deepseek-ai/dsh-client-ui-trajectory",
   "description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)",
-  "version": "0.1.2-alpha.4",
+  "version": "0.1.2-alpha.5",
   "publishConfig": {
     "access": "public"
   },

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini